KITAQ SERIES • REFERENZAUSGABE
KITAQFC
C-Programmierung für Famicom
Quellenstand: 14. September 2026 · Englische Referenzausgabe: 14. September 2026
Nativer Rust-Compiler und Hilfswerkzeuge
Mit Rust 1.85 oder neuer lassen sich Compiler und sämtliche Hilfswerkzeuge für Windows, Linux, macOS ARM und macOS Intel bauen. Die nativen Programme benötigen kein .NET; die Werkzeuge für Grafikdaten benötigen auch weder Python noch Pillow.
Die Windows-Programme liegen im Repository-Stammverzeichnis, die Linux- und macOS-Programme in den unten aufgeführten bin/-Ordnern. Bewahren Sie lib/ und die Lizenzhinweise zusammen mit den Werkzeugen auf. Unter Linux/macOS setzen Sie mit chmod +x das Ausführungsrecht und ergänzen den PATH, oder verwenden den vollständigen Pfad.
| OS | kitaqfc |
|---|---|
| Windows x64 | kitaqfc.exe |
| Linux x64 | bin/Linux-X64/kitaqfc |
| macOS ARM64 | bin/macOS-ARM64/kitaqfc |
| macOS Intel | bin/macOS-X64/kitaqfc |
kitaqfc · Nativer Rust-Compiler und Hilfswerkzeuge · GitHub Actions
Native Builds und Ausführungsprüfungen waren unter Windows, Linux, macOS ARM und macOS Intel erfolgreich. KITAQGB bestand pro Umgebung 48 Tests und 395 Werkzeugprüfungen, KITAQFC 55 und 401. Rust 1.85 wurde ebenfalls getestet. PUBLIC_DISTRIBUTION.json enthält die Hashwerte der installierten Programme und die Herkunft der Prüfergebnisse. Die öffentlichen GitHub-Actions-Workflows bauen und testen diese Quellen unabhängig.
Gespeicherte Referenzausgaben prüfen ROM-Bytes, Diagnosen und Werkzeugformate. Frühere C#-Emulatorprüfungen bleiben historische Nachweise mit ihren ursprünglichen Quellfingerabdrücken. Sie belegen nicht automatisch sämtliche Rust-APIs, echte Hardware oder einen vollständigen Spielstart über das FDS-BIOS. Das nicht verfügbare ursprüngliche PNG-Skript wurde nach seiner Spezifikation neu implementiert; Bytegleichheit mit ihm kann nicht bestätigt werden.
--attach wird aus Kompatibilitätsgründen akzeptiert, verbindet aber keinen verwalteten Debugger mit dem nativen Programm. Verwenden Sie für das PC-Programm einen nativen Debugger.
KITAQGB · SHA-256 · KITAQFC · SHA-256
Beispiele auf den einzelnen Systemen bauen
Die PowerShell-Skripte verwenden das Windows-Programm im Stammverzeichnis. Unter Linux/macOS übergeben Sie dieselben C-Eingaben und Optionen an den nativen Compiler oder verwenden PowerShell 7. CHR-Grafik und C-Quellen sind getrennte Eingaben; font.chr bewahrt die Schrift der Beispiele.
kitaqfc game.c -I lib -o game.nes --mapper=nromVollständige Beispielprogramme
Enthalten sind alle Programme, die gemeinsamen Header, die bereitgestellte ascii.c und die daraus konvertierten Ressourcen. Namen mit m_ am Anfang bezeichnen Hilfsfunktionen für diese Handbücher. Lesen Sie jeden Build-Befehl zusammen mit dem erwarteten Ergebnis und dem Prüfprotokoll. Führen Sie die Befehle im übergeordneten Ordner der nebeneinanderliegenden Repositories aus.
Skript zur Stapelkompilierung / Ergebnisse und Bildschirmaufnahmen
Den mitgelieferten Zeichensatz anzeigen fc_font.c
Zweck: Zeigt, welche Buchstaben, Ziffern und Satzzeichen die mitgelieferte eigens erstellte Schrift enthält und wie sie gezeichnet werden.
Funktionsweise: m_init bereitet Anzeige und Schrift vor. m_text zeichnet jede Zeile; m_put zeichnet das doppelte Anführungszeichen mit Zeichencode 34.
Bedienung und erwartetes Ergebnis: Prüfen Sie, ob 26 Großbuchstaben, 26 Kleinbuchstaben, 10 Ziffern und 30 Satzzeichen erscheinen und alle Zeichen gut lesbar sind. Es ist keine Eingabe erforderlich.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_font.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_font.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Display the letters, digits and punctuation from the supplied ascii.c font.
// Expected: Uppercase, lowercase, numbers and all 30 supplied punctuation glyphs
#include "fc_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(void) {
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(); }
}Die erste Textausgabe fc_hello.c
Zweck: Zeigt den grundlegenden Ablauf zum Starten einer ROM und zum Zeichnen von Text und einer Zahl im Hintergrund.
Funktionsweise: Nach m_init zeichnet m_text die Begrüßung und m_number formatiert 42. Die abschließende Schleife wartet auf Frames und lässt das Bild sichtbar.
Bedienung und erwartetes Ergebnis: Der Bildschirm zeigt HELLO WORLD und 042. Die führende Null entsteht durch die Dezimaldarstellung mit drei Stellen.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_hello.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_hello.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Display a first message on screen.
// Expected: HELLO WORLD and 042
#include "fc_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(void) {
m_init();
m_text(2,3,"HELLO");
m_text(2,5,"HELLO WORLD");
m_number(42);
while (1) { m_wait(); }
}Mit breiterem Zwischentyp rechnen fc_arithmetic.c
Zweck: Zeigt, wie ein Zwischenergebnis gespeichert wird, das nicht in acht Bit passt.
Funktionsweise: (u16)200 + 100 ergibt 300. Das Programm teilt das Ergebnis durch 10 und wandelt es anschließend zur Anzeige in u8 um. Beachten Sie, dass die Typverbreiterung vor der Addition erfolgt.
Bedienung und erwartetes Ergebnis: 030 steht für das berechnete Ergebnis 30.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_arithmetic.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_arithmetic.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Variables, arithmetic and type conversion.
// Expected: 030
#include "fc_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(void) {
m_init();
m_text(2,3,"ARITHMETIC");
u16 sum;
sum = (u16)200 + 100;
m_number((u8)(sum / 10));
while (1) { m_wait(); }
}Verzweigungen und Schleifen fc_control.c
Zweck: Erprobt for, continue, while und if/else in einer kleinen Berechnung.
Funktionsweise: Die for-Schleife addiert 0 bis 7, überspringt dabei 3 und erhält 25. Die while-Schleife zählt bis 32 weiter; das folgende if weist bei Übereinstimmung den Wert 42 zu.
Bedienung und erwartetes Ergebnis: 042 ist das Endergebnis. Das Programm verwendet eine if/else-Verzweigung.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_control.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_control.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Conditional branches and loops.
// Expected: 042
#include "fc_common.h"
// Exercise loop control and a final branch, then display the selected value 42.
void main(void) {
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<32) { sum++; }
if (sum==32) sum=42; else sum=0;
m_number(sum);
while (1) { m_wait(); }
}Funktionen, Arrays, Strukturen und Zeiger fc_aggregate.c
Zweck: Zeigt, wie Werte gruppiert und über Funktionen und Zeiger angesprochen werden.
Funktionsweise: Das Programm kopiert einen Point mit (10,20) und schreibt dann über einen Zeiger 1 bis 4 in ein Array. Es addiert das Funktionsergebnis 30, die Arraywerte 1, 2 und 4 sowie die Konstante 5.
Bedienung und erwartetes Ergebnis: Die Summe wird als 042 angezeigt. Verfolgen Sie die Strukturkopie, die Schreibzugriffe auf ptr[i] und den Rückgabewert der Funktion.
![Funktionen, Arrays, Strukturen und Zeiger: Die Summe wird als 042 angezeigt. Verfolgen Sie die Strukturkopie, die Schreibzugriffe auf ptr[i] und den Rückgabewert der Funktion.](../verification/fc_aggregate.png)
Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_aggregate.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_aggregate.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Functions, arrays, structures and pointers.
// Expected: 042
#include "fc_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(void) {
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(); }
}Bitflags und Pufferkopie fc_bits_memory.c
Zweck: Packt Flags in ein Byte und kopiert einen Puffer in einen getrennten Speicherbereich.
Funktionsweise: __memset löscht vier Bytes. Das Programm setzt Bit 3, invertiert Bit 1 und kopiert anschließend alle vier Bytes mit __memcpy.
Bedienung und erwartetes Ergebnis: Das erste Zielbyte wird als 010 angezeigt; Bit 3 trägt 8 und Bit 1 trägt 2 bei.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_bits_memory.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_bits_memory.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Bit operations and memory copying.
// Expected: 010
#include "fc_common.h"
// Clear the source bytes, set bits 3 and 1, copy the buffer, and display 8 + 2 = 10.
void main(void) {
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(); }
}Neue Tastendrücke zählen fc_input.c
Zweck: Unterscheidet einen neuen Tastendruck von einer weiterhin gedrückten Taste.
Funktionsweise: In jedem Frame wird input_update aufgerufen. Der Zähler steigt nur, wenn input_pressed(BTN_A) wahr ist.
Bedienung und erwartetes Ergebnis: Der Anfangswert ist 000. Drücken Sie A, um 001 zu erhalten; Gedrückthalten erhöht den Zähler nicht weiter. Loslassen und erneut drücken ergibt 002. Der Bytezähler springt nach 255 auf 000 zurück.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaqfc/lib/input.c kitaq-docs/samples/fc_input.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_input.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Distinguish a new button press from a held button.
// Expected: 000 initially; A increments once per press
#include "fc_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(void) {
m_init();
m_text(2,3,"INPUT");
input_init();
m_number(0);
while (1) { m_wait(); input_update(); if(input_pressed(BTN_A)) { manual_count++; m_number(manual_count); } }
}Q8.8-Festkommawerte multiplizieren fc_fixed.c
Zweck: Zeigt die Umwandlung von Ganzzahlen in Q8.8, die Multiplikation und die Rückwandlung in eine Ganzzahl.
Funktionsweise: fix_from_int wandelt 3 und 4 in Q8.8 um. fix_mul multipliziert sie, und fix_to_int wandelt das Produkt wieder in eine Ganzzahl um.
Bedienung und erwartetes Ergebnis: 012 bedeutet 3 × 4 = 12. Es ist die umgewandelte Ganzzahl, nicht der intern mit 256 skalierte Wert.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaqfc/lib/fixed.c kitaq-docs/samples/fc_fixed.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_fixed.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Calculate with signed Q8.8 fixed-point values.
// Expected: 012
#include "fc_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(void) {
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(); }
}Aus einem festen Objektpool zuweisen fc_entity.c
Zweck: Erzeugt ein Objekt aus einer festen Menge von Speicherplätzen, ohne dynamische Speicherzuweisung.
Funktionsweise: entity_init setzt den Pool zurück. entity_create erzeugt Typ 1 bei (20,30); das Programm prüft auf die Fehler-ID 0xFF, bevor es vx auf 2 setzt.
Bedienung und erwartetes Ergebnis: 001 ist die Anzahl aktiver Objekte, keine ID oder Koordinate. Das Beispiel aktualisiert und zeichnet das Objekt nicht; daher erscheint keine bewegte Figur.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaqfc/lib/entity.c kitaq-docs/samples/fc_entity.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_entity.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Allocate an entity from a fixed-capacity object pool.
// Expected: 001
#include "fc_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(void) {
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(); }
}Einen Diagnosewert im RAM aufzeichnen fc_debug.c
Zweck: Speichert einen benannten Spielwert getrennt von der Bildschirmanzeige in einem Diagnosepuffer.
Funktionsweise: Das Programm initialisiert die Diagnose, setzt die Framekennung auf 1 und zeichnet den Wert 42 unter dem Namen HP auf.
Bedienung und erwartetes Ergebnis: 001 ist die Anzahl der Trace-Einträge. HP=42 liegt im Diagnosepuffer; der Bildschirm allein zeigt oder bestätigt diesen Inhalt nicht.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaqfc/lib/debug.c kitaq-docs/samples/fc_debug.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_debug.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Record diagnostic values in the debug trace.
// Expected: 001; trace log contains HP=42
#include "fc_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(void) {
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(); }
}Einen Positionsverlauf speichern fc_chain.c
Zweck: Zeigt, wie ein Positionsverlauf in einem Ringpuffer gespeichert wird.
Funktionsweise: chain_init bindet ein Array mit Platz für vier Punkte ein. Anschließend zeichnet chain_push_head (20,30) auf.
Bedienung und erwartetes Ergebnis: 042 ist eine feste Abschlussmarkierung, keine Koordinate oder Anzahl. Prüfen Sie trail und points für den gespeicherten Verlauf. Dieses Programm zeichnet keinen nachfolgenden Körper.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaqfc/lib/chain.c kitaq-docs/samples/fc_chain.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_chain.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Store a position history in a ring buffer.
// Expected: 042; history contains (20,30)
#include "fc_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(void) {
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(); }
}Den Framezähler der Bibliothek lesen fc_system.c
Zweck: Zeigt, wie Wartezeiten in Frames den Framezähler der Bibliothek beeinflussen.
Funktionsweise: Nach system_init ruft das Programm system_wait_vblank zweimal auf und zeigt system_get_frame8 an.
Bedienung und erwartetes Ergebnis: Das Ergebnis ist 002. Die Warteschleife verwendet die Handbuch-Hilfsfunktion m_wait; der angezeigte Wert steigt daher nicht weiter.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaqfc/lib/system.c kitaq-docs/samples/fc_system.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_system.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Count completed frame waits.
// Expected: 002
#include "fc_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(void) {
m_init();
m_text(2,3,"SYSTEM");
system_init(); system_wait_vblank(); system_wait_vblank();
m_number(system_get_frame8());
while (1) { m_wait(); }
}Die aktuelle Szenen-ID wählen fc_scene.c
Zweck: Zeigt, wie eine ID zur Unterscheidung von Szenen wie Titelbildschirm und Spiel gewählt wird.
Funktionsweise: Das Programm registriert zwei mit null initialisierte SceneDef-Einträge und wählt mit scene_change die ID 1.
Bedienung und erwartetes Ergebnis: 001 ist die aktuelle von scene_get_current gelieferte ID. In diesem Beispiel sind weder Szenen-Callbacks noch Übergangszeichnungen registriert.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaqfc/lib/runtime.c kitaqfc/lib/scene.c kitaq-docs/samples/fc_scene.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_scene.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// The scene streaming module shares runtime.c queue symbols, even though this demo only changes IDs.
#define MANUAL_FC_RUNTIME 1
// Switch the current scene ID.
// Expected: 001; no callbacks registered
#include "fc_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(void) {
m_init();
m_text(2,3,"SCENE");
scene_init(manual_scenes,2); scene_change(1);
m_number(scene_get_current());
while (1) { m_wait(); }
}Gebrochene Q5.3-Bewegung aufsummieren fc_subpixel.c
Zweck: Behält Subpixelbewegungen als Rest bei und überträgt ganze Pixel auf die Position.
Funktionsweise: Ausgehend von X=40 addiert die Schleife achtmal 2/8 Pixel. Eine Rechtsverschiebung liefert die ganzen Pixel; eine Maske behält den Rest bei.
Bedienung und erwartetes Ergebnis: Die Gesamtbewegung beträgt zwei Pixel; deshalb erscheint X=42 als 042. Dieses Beispiel manueller Integration behandelt positive Bewegung; negative Bewegung benötigt einen entsprechenden Übertrag beim Subtrahieren.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_subpixel.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_subpixel.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Accumulate Q5.3 eighth-pixel motion into an integer coordinate.
// Expected: 042; explicit game-side integration using header constants
#include "fc_common.h"
#include "physics2d.h"
// Integrate eight increments of 2/8 pixel. Carry whole pixels into X and
// retain the fractional remainder with the supplied mask, moving X from 40 to 42.
void main(void) {
m_init();
m_text(2,3,"SUBPIXEL");
KQ2DPosition x; KQ2DFraction frac; KQ2DSpeed speed; u8 i;
// This example demonstrates positive motion only; signed negative motion needs a matching carry/borrow path.
x=40; frac=0; speed=2;
for(i=0;i<8;i++) {
frac=(u8)(frac+speed);
x=(u8)(x+(frac>>KQ2D_SUBPIXEL_BITS));
frac=(u8)(frac & KQ2D_SUBPIXEL_MASK);
}
m_number(x);
while (1) { m_wait(); }
}Den NES-Hintergrund scrollen fc_scroll.c
Zweck: Verschiebt den Hintergrund durch Aktualisieren der PPU-Scrollposition.
Funktionsweise: Nach __scroll_set(0,0) erhöht das Programm phase einmal pro Frame und verwendet den Wert als horizontale Position.
Bedienung und erwartetes Ergebnis: Das Wort SCROLL ändert seine horizontale Position. phase springt von 255 auf 0 zurück. Dies ist kein Beispiel für eine breite Karte über mehrere Nametables.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_scroll.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_scroll.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Scroll the NES background horizontally.
// Expected: title scrolls horizontally
#include "fc_common.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(void) {
m_init();
m_text(2,3,"SCROLL");
__scroll_set(0,0);
while (1) { m_wait(); phase++; __scroll_set(phase,0); }
}Eine Tile-Aktualisierung über NMI anwenden fc_vram_queue.c
Zweck: Reicht aus dem Hauptprogramm eine PPU-Aktualisierung ein, die anschließend im NMI-Ablauf angewendet wird.
Funktionsweise: __vramq_put reiht Tile 52 für Nametable-Adresse 0x2103 ein; __vramq_commit reicht die Aktualisierung ein. Diese Adresse entspricht Tile-Koordinate (3,8).
Bedienung und erwartetes Ergebnis: Bei Hintergrundkoordinate (3,8) erscheint eine einzelne 4. Tile 52 ist in der mitgelieferten Schrift die Ziffer 4.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_vram_queue.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_vram_queue.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Submit a VRAM update for the NMI handler.
// Expected: 4 at (3,8)
#include "fc_common.h"
// Queue tile 52 at nametable address 0x2103, corresponding to (3,8), and
// commit the queue so the NMI update path displays the digit 4.
void main(void) {
m_init();
m_text(2,3,"VRAM QUEUE");
__vramq_put(0x2103,52); __vramq_commit();
while (1) { m_wait(); }
}Ein NES-OAM-Sprite anzeigen fc_sprite.c
Zweck: Bereitet Spritefarben und OAM-Daten vor, um ein Bild unabhängig vom Hintergrund zu zeichnen.
Funktionsweise: Das Programm lädt die Spritepalette, setzt Eintrag 0 auf Position (70,80), Tile 65 und Attribute 0, aktiviert die Darstellung und wiederholt den OAM-DMA.
Bedienung und erwartetes Ergebnis: Ein unbewegtes A erscheint als Sprite. Das Beispiel zeigt OAM-Einrichtung und -Übertragung, keine Animation.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaq-docs/samples/fc_sprite.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_sprite.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Display a sprite using NES OAM.
// Expected: A sprite on screen
#include "fc_common.h"
// Load the sprite palette, assign font tile 65 to OAM slot zero, enable
// background/sprite display, and transfer OAM after each frame wait.
void main(void) {
m_init();
m_text(2,3,"SPRITE");
__palette_sp_load(manual_pal); __sprite_set(0,70,80,65,0);
__ppu_mask_set(0x1E);
while (1) { m_wait(); __oam_dma(); }
}Einen APU-Pulston abspielen fc_sound.c
Zweck: Stellt Steuer-, Timer- und Längenwerte des eingebauten NES-Pulskanals ein.
Funktionsweise: Nach nes_apu_init ruft das Programm nes_sfx_square1(0xBF,400,20) auf. Steuerwert 0xBF wählt konstante Lautstärke und hält den Längenzähler an, sodass der Ton anhält. 20 ist ein Index der Längentabelle, keine Dauer von 20 Frames.
Bedienung und erwartetes Ergebnis: Während 042 angezeigt wird, erklingt ein anhaltender Pulston. Beenden Sie ihn mit nes_apu_silence_all. Die Bildschirmanzeige allein bestätigt keine Tonausgabe.

Quelltext herunterladen / Prüfstatus
.\kitaqfc\kitaqfc.exe kitaqfc/lib/audio.c kitaq-docs/samples/fc_sound.c -I kitaqfc/lib -I kitaq-docs/samples -o out/fc_sound.nes --mapper=nrom --nes-chr=kitaq-docs/samples/font.chr// Play a pulse-channel effect on the built-in NES APU.
// Expected: 042 and a pulse tone
#include "fc_common.h"
#include "audio.h"
// Initialize the APU, trigger pulse channel 1 with the chosen timer/length
// values, and leave the screen displaying 42 while the hardware plays the tone.
void main(void) {
m_init();
m_text(2,3,"SOUND");
// Control BF selects constant volume and halts the length counter, so this tone sustains.
// Call nes_apu_silence_all when the game should stop it; 20 is a length-table index, not frames.
nes_apu_init(); nes_sfx_square1(0xBF,400,20);
m_number(42);
while (1) { m_wait(); }
}Referenz der Compiler-Intrinsics
161 Einträge. Öffnen Sie einen Eintrag, um Syntax, Argumente, Quellhinweise und ein Anwendungsbeispiel zu sehen. Aufruffragmente benötigen zusätzliche Initialisierung und sind keine eigenständigen ROMs. Die Beispiele zeigen, wie vorbereitete Werte an die API übergeben werden; sie belegen keine Prüfung am Gerät.
audio_vblank.h — Musikwiedergabe im VBlank-Interrupt
Die Vierkanalmusik läuft in der NMI weiter. Initialisieren Sie den Treiber, legen Sie ein Musikstück fest und füllen Sie die Warteschlange vor. Aktivieren Sie dann NMI für die Wiedergabe und füllen Sie aus dem Hauptprogramm nach. Die NMI liest eine RAM-Warteschlange. Bei rechtzeitigem Nachfüllen reicht dadurch wenig RAM für einen langen Stream. Jeder Kanal hat unabhängige HOLD/STOP-Befehle.
Ein Datensatz besteht aus fünf Bytes: delay, CH1, CH2, CH3, CH4. Eine Dauer von 1 bis 255 gibt an, wie viele NMI-Ticks der neue Zustand anhält. Null beendet die Wiedergabe; die übrigen vier Bytes werden ignoriert. Tonwerte von 0 bis 71 entsprechen C2 bis B7 (24=C4). Rauschwerte von 0 bis 15 wählen eine Periode im langen Modus, 16 bis 31 im kurzen Modus. Für jeden Kanal gilt: 255=NES_AUDIO_HOLD behält den Zustand bei, 254=NES_AUDIO_STOP schaltet ihn stumm.
Dieser Vierkanaltreiber ist auf NTSC abgestimmt: CH1=Impuls 1, CH2=Impuls 2, CH3=Dreieck und CH4=Rauschen, mit den Indizes 0 bis 3. DMC und Erweiterungsaudio werden nicht wiedergegeben. Verwenden Sie ihn nicht gleichzeitig mit direkten APU-Zugriffen, etwa den Effekten in audio.c. Es gibt weder eine PAL-Tonhöhentabelle noch eine PAL-Tempokorrektur.
Läuft die Dauer ab, ohne dass ein weiterer BGM-Datensatz vorliegt, verstummen die BGM-Noten. Nach Eingang neuer Daten geht es bei einer NMI weiter. Ein unabhängiger SFX läuft weiter. Gezählt werden Eintritte in den Datenmangel, nicht alle leeren Frames. Das Einreihen reiner HOLD-Werte stellt die vorherigen BGM-Noten nicht wieder her. Füllen Sie normalerweise pro Frame nach und halten Sie vor längeren Arbeiten ausreichend Wiedergabezeit in der Warteschlange bereit.
fds.h — FDS-Diskettenoperationen
__fds_available
Meldet, ob das Übersetzungsziel FDS ist, etwa zur Anzeige zielspezifischer Informationen in gemeinsamem Spielcode.
Syntax
u8 __fds_available(void);Rückgabewert
1 bei einem FDS-Build, sonst 0. Dies ist keine Laufzeiterkennung von Peripherie.
Bedingungen und Grenzen
Diese Abfrage ist auch für Cartridge-Ziele wie NROM gültig. Eine FDS-exklusive API innerhalb von if (__fds_available()) wird dadurch nicht von der Zielprüfung ausgenommen. Strukturieren Sie den Quelltext passend zum jeweiligen Ziel.
Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.
Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.
Anwendungsbeispiel
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
fds_result[0] = __fds_available();
m_init();
m_text(2,2,"FDS BUILD SELECTION"); m_wait();
m_text(2,5,"FDS=1 / CARTRIDGE=0"); m_wait();
m_number(fds_result[0]); m_wait();
while (1) { m_wait(); }
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Der FDS-Build zeigt 001, der NROM-Build 000. Derselbe C-Quelltext liefert je nach Übersetzungsziel einen anderen Wert, unabhängig davon, ob eine Diskette eingelegt ist.


Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_available.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_available.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
# Cartridge comparison
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_available.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_available.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/fds.h:7
Return the compile-time FDS mapper selection as 0 or 1; this is not a hardware probe.
kitaqfc/kitaqfc/CodeGenerator.cs:5146
if (funcName == "__fds_available")
{
if (!Need(0)) return true;
EmitAsm("LDA", Imm(isFds ? 1 : 0));
return true;
}__fds_disk_ready
Prüft die Diskettenanwesenheit sofort, sodass ein Einlegehinweis einmal pro Frame ohne Blockieren abfragen kann.
Syntax
u8 __fds_disk_ready(void);Rückgabewert
Gibt 1 zurück, wenn Bit 0 von $4032 gelöscht ist, und 0, wenn es gesetzt ist.
Bedingungen und Grenzen
Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.
Die Prüfung verwendet Bit 0 von $4032, das die Anwesenheit einer Diskette meldet. Sie prüft weder die Laufwerksbereitschaft in Bit 1 noch Dateiexistenz oder Ladeabschluss. „ready“ darf hier nicht als Freigabe zum Start einer Übertragung verstanden werden.
KUROSAKI verwendet ein Teilmodell des FDS-Controllers. Das Beispiel beobachtet Anwesenheit 1, Schutzmaske 0 und Status 0. Die Aufnahme belegt kein Verhalten bei Auswurf, geschützten Medien oder CRC-Fehlern.
Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.
Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.
Anwendungsbeispiel
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
fds_result[0] = __fds_disk_ready();
fds_result[1] = __fds_side();
fds_result[2] = __fds_error();
fds_result[3] = 0;
// Never enter an unbounded wait without first checking disk presence.
if (fds_result[0]) {
__fds_wait_ready(); fds_result[3]++;
__fds_wait_insert(); fds_result[3]++;
}
m_init();
m_text(2,2,"FDS REGISTER STATUS"); m_wait();
m_text(2,5,"PRESENT / PROTECT / STATUS"); m_wait();
m_put(2,7,'0'+fds_result[0]);
m_put(12,7,'0'+fds_result[1]);
m_put(22,7,'0'+fds_result[2]); m_wait();
m_text(2,10,"COMPLETED PRESENCE WAITS"); m_wait();
m_put(2,12,'0'+fds_result[3]); m_wait();
m_text(2,15,"NO FILE TRANSFER TESTED"); m_wait();
while (1) { m_wait(); }
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_status.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_status.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/fds.h:9
Return the inverted disk-absent bit from 4032 as 0 or 1; this checks presence, not transfer completion.
kitaqfc/kitaqfc/CodeGenerator.cs:5160
case "__fds_disk_ready":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(0x4032));
EmitAsm("AND", Imm(0x01));
EmitAsm("EOR", Imm(0x01));
return true;
// Expose status bit 2 as the raw mask value 0 or 4, rather than a normalized Boolean.__fds_error
Liest das FDS-Hardwarestatusregister bei $4030. Dies ist kein gespeicherter Fehler des letzten Diskettenvorgangs.
Syntax
u8 __fds_error(void);Rückgabewert
Gibt den vollständigen Acht-Bit-Wert von $4030 zurück. Bewahren Sie bei der Behandlung von BIOS-Fehlercodes den Rückgabewert von __fds_load_file() oder __fds_save_file() auf.
Bedingungen und Grenzen
Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.
KUROSAKI verwendet ein Teilmodell des FDS-Controllers. Das Beispiel beobachtet Anwesenheit 1, Schutzmaske 0 und Status 0. Die Aufnahme belegt kein Verhalten bei Auswurf, geschützten Medien oder CRC-Fehlern.
Das Lesen des Status quittiert auch Interruptanforderungen. Fragen Sie nicht unkontrolliert ab, wenn ein anderer Handler denselben Status benötigt. KUROSAKI löscht bei diesem Lesezugriff sein ausstehendes FDS-IRQ-Flag.
Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.
Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.
Anwendungsbeispiel
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
fds_result[0] = __fds_disk_ready();
fds_result[1] = __fds_side();
fds_result[2] = __fds_error();
fds_result[3] = 0;
// Never enter an unbounded wait without first checking disk presence.
if (fds_result[0]) {
__fds_wait_ready(); fds_result[3]++;
__fds_wait_insert(); fds_result[3]++;
}
m_init();
m_text(2,2,"FDS REGISTER STATUS"); m_wait();
m_text(2,5,"PRESENT / PROTECT / STATUS"); m_wait();
m_put(2,7,'0'+fds_result[0]);
m_put(12,7,'0'+fds_result[1]);
m_put(22,7,'0'+fds_result[2]); m_wait();
m_text(2,10,"COMPLETED PRESENCE WAITS"); m_wait();
m_put(2,12,'0'+fds_result[3]); m_wait();
m_text(2,15,"NO FILE TRANSFER TESTED"); m_wait();
while (1) { m_wait(); }
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_status.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_status.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/fds.h:13
Read the entire 4030 status register; this is not a stored BIOS error code.
kitaqfc/kitaqfc/CodeGenerator.cs:5173
case "__fds_error":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(0x4030));
return true;__fds_side
Gibt das maskierte Schreibschutzbit aus $4032 zurück. Die Funktion erkennt nicht Diskettenseite A oder B.
Syntax
u8 __fds_side(void);Rückgabewert
Gibt $4032 & 0x04 zurück, also 0 oder 4 und keinen normalisierten booleschen Wert. Hardwarebit 2 meldet Schreibschutz oder eine ausgeworfene Diskette.
Bedingungen und Grenzen
Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.
KUROSAKI verwendet ein Teilmodell des FDS-Controllers. Das Beispiel beobachtet Anwesenheit 1, Schutzmaske 0 und Status 0. Die Aufnahme belegt kein Verhalten bei Auswurf, geschützten Medien oder CRC-Fehlern.
Verwenden Sie zur Schutzprüfung (__fds_side() != 0). Um den Speichererfolg zu beurteilen, benötigen Sie außerdem Diskettenanwesenheit und Rückgabewert des BIOS-Vorgangs.
Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.
Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.
Anwendungsbeispiel
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
fds_result[0] = __fds_disk_ready();
fds_result[1] = __fds_side();
fds_result[2] = __fds_error();
fds_result[3] = 0;
// Never enter an unbounded wait without first checking disk presence.
if (fds_result[0]) {
__fds_wait_ready(); fds_result[3]++;
__fds_wait_insert(); fds_result[3]++;
}
m_init();
m_text(2,2,"FDS REGISTER STATUS"); m_wait();
m_text(2,5,"PRESENT / PROTECT / STATUS"); m_wait();
m_put(2,7,'0'+fds_result[0]);
m_put(12,7,'0'+fds_result[1]);
m_put(22,7,'0'+fds_result[2]); m_wait();
m_text(2,10,"COMPLETED PRESENCE WAITS"); m_wait();
m_put(2,12,'0'+fds_result[3]); m_wait();
m_text(2,15,"NO FILE TRANSFER TESTED"); m_wait();
while (1) { m_wait(); }
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_status.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_status.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/fds.h:11
Return raw status mask 4032 & 04, either 0 or 4. This is not a numbered disk-side identifier.
kitaqfc/kitaqfc/CodeGenerator.cs:5167
case "__fds_side":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(0x4032));
EmitAsm("AND", Imm(0x04));
return true;
// Read status register 4030 directly; the caller receives its complete byte value.__fds_wait_insert
Drückt das Warten auf eine eingelegte Diskette aus. Ruft dieselbe Anwesenheits-Abfrageschleife wie __fds_wait_ready() auf.
Syntax
void __fds_wait_insert(void);Rückgabewert
Kein Rückgabewert.
Bedingungen und Grenzen
Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.
Die Prüfung verwendet Bit 0 von $4032, das die Anwesenheit einer Diskette meldet. Sie prüft weder die Laufwerksbereitschaft in Bit 1 noch Dateiexistenz oder Ladeabschluss. „ready“ darf hier nicht als Freigabe zum Start einer Übertragung verstanden werden.
Es gibt keinen Timeout. Ohne Diskette bleibt der Aufruf blockiert. Fragen Sie __fds_disk_ready() einmal pro Frame ab, wenn das Spiel Anzeige oder Eingaben weiter verarbeiten muss. Das Beispiel prüft vor beiden Warteaufrufen die Anwesenheit. Eine bereits eingelegte Diskette erfüllt die Wartebedingung ohne erneutes Herausnehmen und Einlegen.
KUROSAKI verwendet ein Teilmodell des FDS-Controllers. Das Beispiel beobachtet Anwesenheit 1, Schutzmaske 0 und Status 0. Die Aufnahme belegt kein Verhalten bei Auswurf, geschützten Medien oder CRC-Fehlern.
Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.
Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.
Anwendungsbeispiel
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
fds_result[0] = __fds_disk_ready();
fds_result[1] = __fds_side();
fds_result[2] = __fds_error();
fds_result[3] = 0;
// Never enter an unbounded wait without first checking disk presence.
if (fds_result[0]) {
__fds_wait_ready(); fds_result[3]++;
__fds_wait_insert(); fds_result[3]++;
}
m_init();
m_text(2,2,"FDS REGISTER STATUS"); m_wait();
m_text(2,5,"PRESENT / PROTECT / STATUS"); m_wait();
m_put(2,7,'0'+fds_result[0]);
m_put(12,7,'0'+fds_result[1]);
m_put(22,7,'0'+fds_result[2]); m_wait();
m_text(2,10,"COMPLETED PRESENCE WAITS"); m_wait();
m_put(2,12,'0'+fds_result[3]); m_wait();
m_text(2,15,"NO FILE TRANSFER TESTED"); m_wait();
while (1) { m_wait(); }
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_status.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_status.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/fds.h:17
Alias the same disk-presence wait as __fds_wait_ready; the call can block indefinitely.
kitaqfc/kitaqfc/CodeGenerator.cs:9148
EmitHelperStart("__fds_wait_ready");
string loop = NewGeneratedLabel("fds_wait_ready_loop");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LDA", Mem(0x4032));
EmitAsm("AND", Imm(0x01));
EmitAsm("BNE", Rel(loop));
EmitAsm("RTS");__fds_wait_ready
Wartet in einer CPU-Abfrageschleife, bis eine Diskette vorhanden ist.
Syntax
void __fds_wait_ready(void);Rückgabewert
Kein Rückgabewert.
Bedingungen und Grenzen
Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.
Die Prüfung verwendet Bit 0 von $4032, das die Anwesenheit einer Diskette meldet. Sie prüft weder die Laufwerksbereitschaft in Bit 1 noch Dateiexistenz oder Ladeabschluss. „ready“ darf hier nicht als Freigabe zum Start einer Übertragung verstanden werden.
Es gibt keinen Timeout. Ohne Diskette bleibt der Aufruf blockiert. Fragen Sie __fds_disk_ready() einmal pro Frame ab, wenn das Spiel Anzeige oder Eingaben weiter verarbeiten muss. Das Beispiel prüft vor beiden Warteaufrufen die Anwesenheit. Eine bereits eingelegte Diskette erfüllt die Wartebedingung ohne erneutes Herausnehmen und Einlegen.
KUROSAKI verwendet ein Teilmodell des FDS-Controllers. Das Beispiel beobachtet Anwesenheit 1, Schutzmaske 0 und Status 0. Die Aufnahme belegt kein Verhalten bei Auswurf, geschützten Medien oder CRC-Fehlern.
Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.
Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.
Anwendungsbeispiel
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
fds_result[0] = __fds_disk_ready();
fds_result[1] = __fds_side();
fds_result[2] = __fds_error();
fds_result[3] = 0;
// Never enter an unbounded wait without first checking disk presence.
if (fds_result[0]) {
__fds_wait_ready(); fds_result[3]++;
__fds_wait_insert(); fds_result[3]++;
}
m_init();
m_text(2,2,"FDS REGISTER STATUS"); m_wait();
m_text(2,5,"PRESENT / PROTECT / STATUS"); m_wait();
m_put(2,7,'0'+fds_result[0]);
m_put(12,7,'0'+fds_result[1]);
m_put(22,7,'0'+fds_result[2]); m_wait();
m_text(2,10,"COMPLETED PRESENCE WAITS"); m_wait();
m_put(2,12,'0'+fds_result[3]); m_wait();
m_text(2,15,"NO FILE TRANSFER TESTED"); m_wait();
while (1) { m_wait(); }
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Unter PRESENT / PROTECT / STATUS zeigt der Bildschirm 1 / 0 / 0, unter COMPLETED PRESENCE WAITS danach 2. Beide Anwesenheits-Warteaufrufe sind zurückgekehrt. Das ist kein Erfolgsindikator für einen Dateitransfer. Darunter steht NO FILE TRANSFER TESTED.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_status.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_status.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/fds.h:15
Poll disk presence until the absent bit clears. There is no timeout or cancellation path.
kitaqfc/kitaqfc/CodeGenerator.cs:9148
EmitHelperStart("__fds_wait_ready");
string loop = NewGeneratedLabel("fds_wait_ready_loop");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LDA", Mem(0x4032));
EmitAsm("AND", Imm(0x01));
EmitAsm("BNE", Rel(loop));
EmitAsm("RTS");fds_file.h — Laden von FDS-Dateien
__fds_file_exists
Prüft, ob die kompilierten Dateimetadaten die angeforderte ID enthalten. Auch ein Datensatz mit Größe null gilt als vorhanden.
Syntax
u8 __fds_file_exists(u8 id);Parameter
idZu suchende Datei-ID. Metadaten nutzen IDs von 0 bis 254; 255 ist als Abschlusswert reserviert. Eine ID unterscheidet sich von Dateinummer, Dateiname und Zieladresse.
Rückgabewert
1, wenn die kompilierte Tabelle die ID enthält, sonst 0. Das garantiert nicht, dass die eingelegte Diskette diese Datei enthält.
Bedingungen und Grenzen
Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.
Durchsucht die aus --fds-meta=... oder --fds-manifest=... kompilierte Tabelle zusammen mit erzeugten Overlayeinträgen. Es wird kein Diskettenverzeichnis gelesen. Ohne Tabelleneintrag wird eine Datei selbst dann nicht gefunden, wenn sie im Diskettenabbild enthalten ist. Manuelle und erzeugte Einträge sind zusammen auf 42 begrenzt.
Die Größe ist der in den Metadaten deklarierte Wert. Sie muss weder der ermittelten Quelldateilänge noch der Nutzdatenlänge eines gekürzten Overlays oder dem Inhalt einer gewechselten Diskette entsprechen. Deklarieren Sie korrekte Größen, wenn Sie damit Datenzugriffe begrenzen.
Legen Sie fds_metadata.json und die beiden zugehörigen .bin-Dateien neben den C-Quelltext. ID 16 enthält eine selbst erstellte Folge von 513 Bytes: zweimal 0 bis 255, danach null. ID 17 enthält keine Nutzdaten. Beide sind Nicht-Boot-PRG-Dateien (Typ 0) und werden in dieser Lektion nicht geladen. Relative Quellpfade werden vom Manifestverzeichnis aus aufgelöst.
Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.
Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.
Anwendungsbeispiel
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
fds_result[0] = __fds_file_exists(16);
fds_sizes[0] = __fds_file_size(16);
fds_result[1] = __fds_file_exists(17);
fds_sizes[1] = __fds_file_size(17);
fds_result[2] = __fds_file_exists(18);
fds_sizes[2] = __fds_file_size(18);
m_init();
m_text(2,2,"FDS COMPILED FILE TABLE"); m_wait();
m_text(2,5,"ID EXISTS BYTES"); m_wait();
m_text(2,7,"16 1 513"); m_wait();
m_text(2,9,"17 1 0"); m_wait();
m_text(2,11,"18 0 0"); m_wait();
if (fds_result[0]==1 && fds_sizes[0]==513 &&
fds_result[1]==1 && fds_sizes[1]==0 &&
fds_result[2]==0 && fds_sizes[2]==0) { m_text(2,14,"TABLE VALUES MATCH: PASS"); }
else { m_text(2,14,"TABLE VALUES MATCH: FAIL"); }
m_wait();
m_text(2,17,"NO DISK DIRECTORY SCAN"); m_wait();
while (1) { m_wait(); }
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Die Tabelle zeigt ID 16 als vorhanden mit 513 Bytes, ID 17 als vorhanden mit null Bytes und ID 18 als nicht vorhanden mit null Bytes. TABLE VALUES MATCH: PASS vergleicht die abgefragten Werte mit dieser Kombination. Größe null allein unterscheidet keinen leeren Datensatz von einer fehlenden ID.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_metadata.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_metadata.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm --fds-meta=.\kitaq-docs\samples\api-examples\fc\fds_metadata.jsonImplementierung und Quelltext
kitaqfc/lib/fds_file.h:18
Query the compiled --fds-meta/--fds-manifest table, not the inserted disk; return 0 or 1.
kitaqfc/kitaqfc/CodeGenerator.cs:9481
EmitHelperStart("__fds_file_exists");
EmitFdsMetadataSearch(foundReturnsSize: false);
// Return the compiled metadata size, using zero for an unmatched ID.__fds_file_size
Gibt die in den kompilierten Metadaten für eine ID deklarierte Bytegröße zurück, etwa zur Prüfung der vorgesehenen Ladegröße einer Ressource.
Syntax
u16 __fds_file_size(u8 id);Parameter
idZu suchende Datei-ID. Metadaten nutzen IDs von 0 bis 254; 255 ist als Abschlusswert reserviert. Eine ID unterscheidet sich von Dateinummer, Dateiname und Zieladresse.
Rückgabewert
Deklarierte vorzeichenlose 16-Bit-Größe von 0 bis 65535. Fehlende IDs liefern ebenfalls null; verwenden Sie __fds_file_exists(), wenn die Unterscheidung wichtig ist.
Bedingungen und Grenzen
Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.
Durchsucht die aus --fds-meta=... oder --fds-manifest=... kompilierte Tabelle zusammen mit erzeugten Overlayeinträgen. Es wird kein Diskettenverzeichnis gelesen. Ohne Tabelleneintrag wird eine Datei selbst dann nicht gefunden, wenn sie im Diskettenabbild enthalten ist. Manuelle und erzeugte Einträge sind zusammen auf 42 begrenzt.
Die Größe ist der in den Metadaten deklarierte Wert. Sie muss weder der ermittelten Quelldateilänge noch der Nutzdatenlänge eines gekürzten Overlays oder dem Inhalt einer gewechselten Diskette entsprechen. Deklarieren Sie korrekte Größen, wenn Sie damit Datenzugriffe begrenzen.
Legen Sie fds_metadata.json und die beiden zugehörigen .bin-Dateien neben den C-Quelltext. ID 16 enthält eine selbst erstellte Folge von 513 Bytes: zweimal 0 bis 255, danach null. ID 17 enthält keine Nutzdaten. Beide sind Nicht-Boot-PRG-Dateien (Typ 0) und werden in dieser Lektion nicht geladen. Relative Quellpfade werden vom Manifestverzeichnis aus aufgelöst.
Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.
Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.
Anwendungsbeispiel
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
fds_result[0] = __fds_file_exists(16);
fds_sizes[0] = __fds_file_size(16);
fds_result[1] = __fds_file_exists(17);
fds_sizes[1] = __fds_file_size(17);
fds_result[2] = __fds_file_exists(18);
fds_sizes[2] = __fds_file_size(18);
m_init();
m_text(2,2,"FDS COMPILED FILE TABLE"); m_wait();
m_text(2,5,"ID EXISTS BYTES"); m_wait();
m_text(2,7,"16 1 513"); m_wait();
m_text(2,9,"17 1 0"); m_wait();
m_text(2,11,"18 0 0"); m_wait();
if (fds_result[0]==1 && fds_sizes[0]==513 &&
fds_result[1]==1 && fds_sizes[1]==0 &&
fds_result[2]==0 && fds_sizes[2]==0) { m_text(2,14,"TABLE VALUES MATCH: PASS"); }
else { m_text(2,14,"TABLE VALUES MATCH: FAIL"); }
m_wait();
m_text(2,17,"NO DISK DIRECTORY SCAN"); m_wait();
while (1) { m_wait(); }
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Die Tabelle zeigt ID 16 als vorhanden mit 513 Bytes, ID 17 als vorhanden mit null Bytes und ID 18 als nicht vorhanden mit null Bytes. TABLE VALUES MATCH: PASS vergleicht die abgefragten Werte mit dieser Kombination. Größe null allein unterscheidet keinen leeren Datensatz von einer fehlenden ID.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_metadata.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_metadata.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm --fds-meta=.\kitaq-docs\samples\api-examples\fc\fds_metadata.jsonImplementierung und Quelltext
kitaqfc/lib/fds_file.h:20
Return the compiled metadata size, or zero if id is absent. No disk directory is read.
kitaqfc/kitaqfc/CodeGenerator.cs:9485
EmitHelperStart("__fds_file_size");
EmitFdsMetadataSearch(foundReturnsSize: true);__fds_load_file
Liest eine Datei über das BIOS und kopiert deren PRG-Daten in einen CPU-Puffer. Damit laden Sie Speicherdaten oder Levelressourcen in vom Spiel reservierten Speicher.
Syntax
u8 __fds_load_file(u8 id, u8* dst);Parameter
idID einer im erzeugten Diskettenabbild enthaltenen Nicht-Bootdatei, 0 bis 254; das Beispiel verwendet 73. Sie unterscheidet sich von der physischen Dateiposition. ID 255, nicht vorhandene IDs und Bootdateien werden abgewiesen.
dstZieladresse einer PRG-Datei mit Platz für ihre vollständige gepackte Dateilänge. Übergeben Sie null, um die Daten ohne Kopie am im Header festgelegten Ziel zu belassen. Für CHR- oder Nametable-PPU-Dateien ist nur null erlaubt; das BIOS überträgt an das native PPU-Ziel.
Rückgabewert
Bei Erfolg null. BIOS-Fehler ungleich null werden unverändert zurückgegeben. Meldet das BIOS Erfolg, ohne genau eine Datei geladen zu haben, lautet der Rückgabewert 64 ($40). Ungültige IDs, Puffer oder Kombinationen aus Dateityp und Ziel ergeben 255 ($FF).
Bedingungen und Grenzen
Verwenden Sie die API mit --mapper=fds --fds-layout=fds32. Das Manifest --fds-meta definiert Datei-IDs, Ladeadressen, Typen, Bootstatus und Reihenfolge. Übertragungsadressen und -größen stammen aus dem beim Build erzeugten Diskettenabbild. Die API untersucht keine beliebig eingelegte Diskette.
Das BIOS lädt zunächst an die im Diskettenheader gespeicherte Adresse; anschließend kopiert diese Hilfsfunktion nach dst. Sie wählt die Kopierrichtung so, dass überlappende Daten erhalten bleiben. Auch der Zwischenbereich muss jedoch reserviert sein. Bei einem fehlgeschlagenen Transfer wird nicht nach dst kopiert. Nach einem BIOS-Aufruf wird die Bankbelegung mit 255 (unbekannt) vermerkt.
Jeder CPU-Puffer muss vollständig entweder in $0200–$07FF oder in $6000–$DFFF liegen. Überlaufende Adressbereiche und Ziele in E/A-Registern werden abgewiesen. Der Aufrufer muss außerdem Überschneidungen mit Code, OAM, anderen Variablen und Compiler-Zwischenspeicher vermeiden. Das Beispiel nutzt Bank 0 für gemeinsamen Code, $8000 als Zwischenbereich und Vier-Byte-Puffer bei $8500 und $8600.
Dies sind synchrone BIOS-Aufrufe. Rufen Sie sie aus gemeinsamem Code auf, damit die Übertragung weder laufenden Code noch dessen Rücksprungadresse überschreibt, und nicht aus einem Interrupt-Handler. Erfüllen Sie während Disketten-E/A die BIOS-Voraussetzungen: Darstellung und NMI stoppen, PPU-Adressschritt auf eins setzen und alle IRQ-Quellen außer der Diskette deaktivieren. Ein unterbrechungsfreier Spiel- oder Musikablauf während der Übertragung ist nicht garantiert.
Die Aufnahme verwendet einen eigens geschriebenen Test für die BIOS-Aufrufkonvention, um kompilierten CPU-Code, Pufferinhalte, Speichern und Zurücklesen sowie jedes angezeigte Pixel zu prüfen. Das echte BIOS, physische Diskettenschreibvorgänge, CRC und Wiederherstellung nach Stromausfall werden damit nicht geprüft. Eine gewöhnliche FDS-Ausführung in KUROSAKI allein belegt nicht dasselbe Speicherverhalten.
Verwenden Sie das Beispiel zusammen mit fds_file_io_manifest.json und fds_file_io_payload.bin aus demselben Ordner. Das folgende Prüfprogramm erzeugt aus eigenem Quellcode einen temporären Testbaustein und entfernt ihn anschließend. Es ist kein kommerzielles BIOS enthalten; der Testbaustein ist kein BIOS für normale Spiele.
Anwendungsbeispiel
#pragma fixed_bank 0
#include "fc_fds_scene.h"
void main(void) {
u8 i;
u8* data=(u8*)0x8500;
u8* again=(u8*)0x8600;
__irq_disable();__ppu_ctrl_set(0);__ppu_mask_set(0);
// Keep rendering/NMI and non-disk IRQ sources disabled during disk I/O.
*((u8*)0xFF)=0;*((u8*)0x4010)=0;*((u8*)0x4017)=0x40;*((u8*)0x4022)=0;
fds_result[0]=__fds_load_file(73,data);
fds_result[1]=data[0];fds_result[2]=data[3];
for(i=0;i<4;i=i+1){data[i]=(i+1)*7;}
fds_result[3]=__fds_save_file(73,data,4);
fds_result[4]=__fds_load_file(73,again);
fds_result[5]=again[0];fds_result[6]=again[3];
// Check every returned byte, not only the displayed endpoints.
for(i=0;i<4;i=i+1){if(again[i]!=(i+1)*7){fds_result[7]=1;}}
fds_result[15]=165;
m_init();m_text(2,2,"FDS LOAD / SAVE BUFFER");m_wait();
m_text(2,5,"LOAD TO 8500 STATUS");fds_number(25,5,fds_result[0]);m_wait();
m_text(2,7,"INITIAL FIRST/LAST");fds_number(21,7,fds_result[1]);fds_number(25,7,fds_result[2]);m_wait();
m_text(2,9,"SAVE FILE 73 STATUS");fds_number(25,9,fds_result[3]);m_wait();
m_text(2,11,"RELOAD TO 8600 STATUS");fds_number(25,11,fds_result[4]);m_wait();
m_text(2,13,"SAVED FIRST / LAST");fds_number(21,13,fds_result[5]);fds_number(25,13,fds_result[6]);m_wait();
m_text(2,15,"BYTE CHECK ERRORS");fds_number(25,15,fds_result[7]);m_wait();
m_text(2,18,"STAGING 8000 / SIZE 4");m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
LOAD, SAVE, RELOAD STATUS und BYTE CHECK ERRORS zeigen jeweils 000. INITIAL FIRST/LAST zeigt 011 und 044, den ersten und letzten der vier nach $8500 geladenen Bytes. Das Beispiel ändert sie in 7, 14, 21 und 28, speichert sie und lädt sie nach $8600 zurück. Daher zeigt SAVED FIRST / LAST 007 und 028. STAGING 8000 / SIZE 4 bezeichnet die im Diskettenabbild festgelegte Ladeadresse und Dateilänge. Der separate Test prüft außerdem, dass ID 73 in der tatsächlich vierten Datei, also der nullbasierten Position 3, gespeichert wird.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_file_io.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_file_io.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --fds-meta=.\kitaq-docs\samples\api-examples\fc\fds_file_io_manifest.json --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
python .\kitaq-docs\tools\check_fds_file_examples.pyImplementierung und Quelltext
kitaqfc/lib/fds_file.h:11
Native FDS32: load a non-boot file at its disk-header address, then memmove PRG bytes to dst. Null dst keeps the native destination (also for PPU files). Both CPU spans must fit $0200-$07FF or $6000-$DFFF and be owned by the caller. Return 0, a BIOS error, $40 for a missing transfer, or $FF for invalid input. Call from common code with disk-I/O interrupt/display prerequisites satisfied.
kitaqfc/kitaqfc/CodeGenerator.cs:9223
EmitHelperStart("__fds_load_file");
// Load at the on-disk staging address, then memmove PRG data to non-null dst.
EmitAsm("JSR", Abs("__kq_fds_find_io"));
EmitAsm("CMP", Imm(0));
EmitAsm("BNE", Rel("__kq_fio_load_invalid"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 19));
EmitAsm("BMI", Rel("__kq_fio_load_invalid"));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 14));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 15));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 23));
EmitAsm("STA", Mem(CallArgBase + 10));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 24));
EmitAsm("STA", Mem(CallArgBase + 11));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 20));
EmitAsm("BNE", Rel("__kq_fio_load_ppu"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 21));
EmitAsm("STA", Mem(CallArgBase + 8));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 22));
EmitAsm("STA", Mem(CallArgBase + 9));
EmitAsm("JSR", Abs("__kq_fds_check_span"));
EmitAsm("CMP", Imm(0));
EmitAsm("BNE", Rel("__kq_fio_load_invalid"));
EmitAsm("JMP", Abs("__kq_fio_load_dst"));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_load_ppu"));
// CHR/nametable files have no CPU buffer; null dst selects their native PPU destination.
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 14));
EmitAsm("ORA", Mem(_runtimeFdsFileHeaderAddress + 15));
EmitAsm("BNE", Rel("__kq_fio_load_invalid"));
EmitAsm("JMP", Abs("__kq_fio_load_bios"));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_load_dst"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 14));
EmitAsm("ORA", Mem(_runtimeFdsFileHeaderAddress + 15));
EmitAsm("BEQ", Rel("__kq_fio_load_bios"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 14));
EmitAsm("STA", Mem(CallArgBase + 8));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 15));
EmitAsm("STA", Mem(CallArgBase + 9));
EmitAsm("JSR", Abs("__kq_fds_check_span"));
EmitAsm("CMP", Imm(0));
EmitAsm("BNE", Rel("__kq_fio_load_invalid"));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_load_bios"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 17));
EmitAsm("STA", Mem(_runtimeFdsLoadListAddress));
EmitAsm("LDA", Imm(0xFF));
EmitAsm("STA", Mem(_runtimeFdsLoadListAddress + 1));
EmitAsm("JSR", Mem(0xE1F8));
_assembly.Add(Expr.Make(Tag.Word, string.Format("${0:X4}", _runtimeFdsFileHeaderAddress + 33)));
_assembly.Add(Expr.Make(Tag.Word, string.Format("${0:X4}", _runtimeFdsLoadListAddress)));
EmitAsm("TAX");
EmitAsm("LDA", Imm(0xFF));
EmitAsm("STA", Mem(_runtimeFdsResidentBankAddress));
EmitAsm("STA", Mem(_runtimeCurrentBankAddress));
EmitAsm("TXA");
EmitAsm("BNE", Rel("__kq_fio_load_return"));
EmitAsm("CPY", Imm(1));
EmitAsm("BEQ", Rel("__kq_fio_load_count_ok"));
EmitAsm("LDA", Imm(0x40));
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_load_count_ok"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 14));
EmitAsm("ORA", Mem(_runtimeFdsFileHeaderAddress + 15));
EmitAsm("BEQ", Rel("__kq_fio_load_ok"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 21));
EmitAsm("STA", Mem(CallArgBase + 0));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 14));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 23));
EmitAsm("STA", Mem(CallArgBase + 4));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 22));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 15));
EmitAsm("STA", Mem(CallArgBase + 3));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 24));
EmitAsm("STA", Mem(CallArgBase + 5));
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BEQ", Rel("__kq_fio_load_ok"));
// Copy backwards when destination starts above source, including overlapping buffers.
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("CMP", Mem(CallArgBase + 1));
EmitAsm("BCC", Rel("__kq_fio_forward"));
EmitAsm("BNE", Rel("__kq_fio_back_setup"));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("CMP", Mem(CallArgBase + 0));
EmitAsm("BCC", Rel("__kq_fio_forward"));
EmitAsm("BEQ", Rel("__kq_fio_load_ok"));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_back_setup"));
EmitAsm("CLC");
EmitAsm("LDA", Mem(CallArgBase + 0));
EmitAsm("ADC", Mem(CallArgBase + 4));
EmitAsm("STA", Mem(CallArgBase + 0));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("ADC", Mem(CallArgBase + 5));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("CLC");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("ADC", Mem(CallArgBase + 4));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ADC", Mem(CallArgBase + 5));
EmitAsm("STA", Mem(CallArgBase + 3));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_backward"));
EmitAsm("LDA", Mem(CallArgBase + 0));
EmitAsm("BNE", Rel("__kq_fio_back_dec0"));
EmitAsm("DEC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_back_dec0"));
EmitAsm("DEC", Mem(CallArgBase + 0));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("BNE", Rel("__kq_fio_back_dec2"));
EmitAsm("DEC", Mem(CallArgBase + 3));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_back_dec2"));
EmitAsm("DEC", Mem(CallArgBase + 2));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("STA", IndY(CallArgBase + 2));
EmitAsm("JSR", Abs("__kq_fds_copy_count"));
EmitAsm("BNE", Rel("__kq_fio_backward"));
EmitAsm("JMP", Abs("__kq_fio_load_ok"));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_forward"));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("STA", IndY(CallArgBase + 2));
EmitAsm("INC", Mem(CallArgBase + 0));
EmitAsm("BNE", Rel("__kq_fio_forward_inc0"));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_forward_inc0"));
EmitAsm("INC", Mem(CallArgBase + 2));
EmitAsm("BNE", Rel("__kq_fio_forward_inc2"));
EmitAsm("INC", Mem(CallArgBase + 3));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_forward_inc2"));
EmitAsm("JSR", Abs("__kq_fds_copy_count"));
EmitAsm("BNE", Rel("__kq_fio_forward"));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_load_ok"));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_load_return"));
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_load_invalid"));
EmitAsm("LDA", Imm(0xFF));
EmitAsm("RTS");__fds_save_file
Überschreibt einen reservierten PRG-Speicherplatz fester Größe am Ende einer Diskettenseite mit Bytes aus einem CPU-Puffer. Datei-ID, Name und künftige Ladeadresse bleiben erhalten.
Syntax
u8 __fds_save_file(u8 id, const u8* src, u16 len);Parameter
idID einer im erzeugten Diskettenabbild enthaltenen Nicht-Bootdatei, 0 bis 254; das Beispiel verwendet 73. Sie unterscheidet sich von der physischen Dateiposition. ID 255, nicht vorhandene IDs und Bootdateien werden abgewiesen.
srcAnfang der Daten im CPU-RAM, über len Bytes lesbar. Diese API speichert VRAM nicht direkt.
lenAnzahl zu speichernder Bytes; sie muss der Dateilänge im erzeugten Abbild entsprechen. Hat das Spiel weniger Daten, füllen Sie vor dem Aufruf auch den ungenutzten Teil des Speicherpuffers.
Rückgabewert
Bei Erfolg null, sonst ein BIOS-Fehler wie Schreibschutz oder fehlgeschlagene Schreibprüfung. Bei ungültiger ID, Boot- oder Nicht-PRG-Datei, einer nicht letzten Datei auf ihrer Seite, abweichender Länge oder ungültigem CPU-Adressbereich wird ohne BIOS-Aufruf 255 ($FF) zurückgegeben.
Bedingungen und Grenzen
Verwenden Sie die API mit --mapper=fds --fds-layout=fds32. Das Manifest --fds-meta definiert Datei-IDs, Ladeadressen, Typen, Bootstatus und Reihenfolge. Übertragungsadressen und -größen stammen aus dem beim Build erzeugten Diskettenabbild. Die API untersucht keine beliebig eingelegte Diskette.
BIOS WriteFile verbirgt logisch alle Dateien hinter der Schreibposition. Daher akzeptiert diese API nur die letzte Nicht-Boot-PRG-Datei jeder Seite. Sie hängt keine neue Datei an und ändert keine Speicherplatzgröße. Der Speicherplatz muss auch im endgültigen Layout mit erzeugten Boothelfern und automatischen Overlays an letzter Stelle stehen.
Der Compiler unterscheidet Datei-ID, Headernummer und physische Reihenfolge. Das Beispiel hat ID 73 und Manifestnummer 200, übergibt aber Position 3 an das BIOS. Auch Disketten- und Seitennummern stammen aus dem aufgelösten Layout. Verwenden Sie dies nicht mit einer Diskette, deren Layout unabhängig vom Manifest geändert wurde.
Jeder CPU-Puffer muss vollständig entweder in $0200–$07FF oder in $6000–$DFFF liegen. Überlaufende Adressbereiche und Ziele in E/A-Registern werden abgewiesen. Der Aufrufer muss außerdem Überschneidungen mit Code, OAM, anderen Variablen und Compiler-Zwischenspeicher vermeiden. Das Beispiel nutzt Bank 0 für gemeinsamen Code, $8000 als Zwischenbereich und Vier-Byte-Puffer bei $8500 und $8600.
Dies sind synchrone BIOS-Aufrufe. Rufen Sie sie aus gemeinsamem Code auf, damit die Übertragung weder laufenden Code noch dessen Rücksprungadresse überschreibt, und nicht aus einem Interrupt-Handler. Erfüllen Sie während Disketten-E/A die BIOS-Voraussetzungen: Darstellung und NMI stoppen, PPU-Adressschritt auf eins setzen und alle IRQ-Quellen außer der Diskette deaktivieren. Ein unterbrechungsfreier Spiel- oder Musikablauf während der Übertragung ist nicht garantiert.
Die Aufnahme verwendet einen eigens geschriebenen Test für die BIOS-Aufrufkonvention, um kompilierten CPU-Code, Pufferinhalte, Speichern und Zurücklesen sowie jedes angezeigte Pixel zu prüfen. Das echte BIOS, physische Diskettenschreibvorgänge, CRC und Wiederherstellung nach Stromausfall werden damit nicht geprüft. Eine gewöhnliche FDS-Ausführung in KUROSAKI allein belegt nicht dasselbe Speicherverhalten.
Verwenden Sie das Beispiel zusammen mit fds_file_io_manifest.json und fds_file_io_payload.bin aus demselben Ordner. Das folgende Prüfprogramm erzeugt aus eigenem Quellcode einen temporären Testbaustein und entfernt ihn anschließend. Es ist kein kommerzielles BIOS enthalten; der Testbaustein ist kein BIOS für normale Spiele.
Anwendungsbeispiel
#pragma fixed_bank 0
#include "fc_fds_scene.h"
void main(void) {
u8 i;
u8* data=(u8*)0x8500;
u8* again=(u8*)0x8600;
__irq_disable();__ppu_ctrl_set(0);__ppu_mask_set(0);
// Keep rendering/NMI and non-disk IRQ sources disabled during disk I/O.
*((u8*)0xFF)=0;*((u8*)0x4010)=0;*((u8*)0x4017)=0x40;*((u8*)0x4022)=0;
fds_result[0]=__fds_load_file(73,data);
fds_result[1]=data[0];fds_result[2]=data[3];
for(i=0;i<4;i=i+1){data[i]=(i+1)*7;}
fds_result[3]=__fds_save_file(73,data,4);
fds_result[4]=__fds_load_file(73,again);
fds_result[5]=again[0];fds_result[6]=again[3];
// Check every returned byte, not only the displayed endpoints.
for(i=0;i<4;i=i+1){if(again[i]!=(i+1)*7){fds_result[7]=1;}}
fds_result[15]=165;
m_init();m_text(2,2,"FDS LOAD / SAVE BUFFER");m_wait();
m_text(2,5,"LOAD TO 8500 STATUS");fds_number(25,5,fds_result[0]);m_wait();
m_text(2,7,"INITIAL FIRST/LAST");fds_number(21,7,fds_result[1]);fds_number(25,7,fds_result[2]);m_wait();
m_text(2,9,"SAVE FILE 73 STATUS");fds_number(25,9,fds_result[3]);m_wait();
m_text(2,11,"RELOAD TO 8600 STATUS");fds_number(25,11,fds_result[4]);m_wait();
m_text(2,13,"SAVED FIRST / LAST");fds_number(21,13,fds_result[5]);fds_number(25,13,fds_result[6]);m_wait();
m_text(2,15,"BYTE CHECK ERRORS");fds_number(25,15,fds_result[7]);m_wait();
m_text(2,18,"STAGING 8000 / SIZE 4");m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
LOAD, SAVE, RELOAD STATUS und BYTE CHECK ERRORS zeigen jeweils 000. INITIAL FIRST/LAST zeigt 011 und 044, den ersten und letzten der vier nach $8500 geladenen Bytes. Das Beispiel ändert sie in 7, 14, 21 und 28, speichert sie und lädt sie nach $8600 zurück. Daher zeigt SAVED FIRST / LAST 007 und 028. STAGING 8000 / SIZE 4 bezeichnet die im Diskettenabbild festgelegte Ladeadresse und Dateilänge. Der separate Test prüft außerdem, dass ID 73 in der tatsächlich vierten Datei, also der nullbasierten Position 3, gespeichert wird.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_file_io.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_file_io.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --fds-meta=.\kitaq-docs\samples\api-examples\fc\fds_file_io_manifest.json --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
python .\kitaq-docs\tools\check_fds_file_examples.pyImplementierung und Quelltext
kitaqfc/lib/fds_file.h:16
Overwrite the final non-boot PRG slot on its side, preserving its ID/name/load address. len must equal its packaged size; src must be a valid CPU RAM span. The resolved physical ordinal selects the slot, independently of file ID. Return 0, a BIOS error, or $FF before any write for an invalid slot/span/size.
kitaqfc/kitaqfc/CodeGenerator.cs:9488
EmitHelperStart("__fds_save_file");
// Only a non-boot, final, fixed-size PRG slot can be overwritten without hiding later files.
EmitAsm("JSR", Abs("__kq_fds_find_io"));
EmitAsm("CMP", Imm(0));
EmitAsm("BNE", Rel("__kq_fio_save_invalid"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 19));
EmitAsm("AND", Imm(0xC0));
EmitAsm("CMP", Imm(0x40));
EmitAsm("BNE", Rel("__kq_fio_save_invalid"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 20));
EmitAsm("BNE", Rel("__kq_fio_save_invalid"));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("CMP", Mem(_runtimeFdsFileHeaderAddress + 23));
EmitAsm("BNE", Rel("__kq_fio_save_invalid"));
EmitAsm("STA", Mem(CallArgBase + 10));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(CallArgBase + 8));
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("CMP", Mem(_runtimeFdsFileHeaderAddress + 24));
EmitAsm("BNE", Rel("__kq_fio_save_invalid"));
EmitAsm("STA", Mem(CallArgBase + 11));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(CallArgBase + 9));
EmitAsm("JSR", Abs("__kq_fds_check_span"));
EmitAsm("CMP", Imm(0));
EmitAsm("BNE", Rel("__kq_fio_save_invalid"));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 17));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 0));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 25));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 1));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 26));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 2));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 27));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 3));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 28));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 4));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 29));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 5));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 30));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 6));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 31));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 7));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 32));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 8));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 21));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 9));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 11));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 14));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 22));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 10));
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 12));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 15));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 13));
EmitAsm("STA", Mem(_runtimeFdsFileHeaderAddress + 16));
EmitAsm("LDA", Mem(_runtimeFdsFileHeaderAddress + 18));
EmitAsm("JSR", Mem(0xE239));
_assembly.Add(Expr.Make(Tag.Word, string.Format("${0:X4}", _runtimeFdsFileHeaderAddress + 33)));
_assembly.Add(Expr.Make(Tag.Word, string.Format("${0:X4}", _runtimeFdsFileHeaderAddress)));
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, "__kq_fio_save_invalid"));
EmitAsm("LDA", Imm(0xFF));
EmitAsm("RTS");
// Enable wave-RAM writes, copy 64 source bytes, then clear the wave-write control register.fds_overlay.h — FDS-Overlaycode
__fds_current_bank
Gibt die als resident vermerkte logische Bank zurück, ohne von der Diskette zu lesen.
Syntax
u8 __fds_current_bank(void);Rückgabewert
Acht-Bit-Softwarevermerk der Bankbelegung: direkt nach dem normalen Start 1, bei unbekannter Belegung 255.
Bedingungen und Grenzen
Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.
Der normale FDS-Start setzt den Belegungsvermerk auf 1. __fds_load_bank() aktualisiert ihn nach erfolgreichem Laden. Direkte Ladevorgänge über __fds_load_file() oder __fds_load_overlay() sowie fehlgeschlagene Banktransfers setzen ihn auf 255 (unbekannt). Speicherinhalte werden nicht geprüft. Verlassen Sie sich nach direkter Änderung des Fensters nicht auf diesen Vermerk. Eine eigene Resetroutine muss die Initialisierung selbst übernehmen.
Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.
Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.
Anwendungsbeispiel
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
request_overlay=0;
fds_result[0] = __fds_current_bank();
fds_result[1] = __fds_is_bank_resident(1);
fds_result[2] = __fds_is_bank_resident(2);
fds_result[3] = __fds_overlay_function_count();
if (request_overlay) {
fds_result[4]=lesson_overlay_a(); fds_result[5]=lesson_overlay_b();
}
m_init();
m_text(2,2,"FDS OVERLAY RECORDS"); m_wait();
m_text(2,5,"CURRENT BANK"); m_put(25,5,'0'+fds_result[0]); m_wait();
m_text(2,7,"BANK 1 RESIDENT"); m_put(25,7,'0'+fds_result[1]); m_wait();
m_text(2,9,"BANK 2 RESIDENT"); m_put(25,9,'0'+fds_result[2]); m_wait();
m_text(2,11,"OVERLAY FUNCTIONS"); m_put(25,11,'0'+fds_result[3]); m_wait();
m_text(2,14,"NO OVERLAY LOAD TESTED"); m_wait();
while (1) { m_wait(); }
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.


Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_residency.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_residency.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/fds_overlay.h:16
Return the software resident-bank byte, or FF after a failed bank transfer. This is not a checksum of overlay memory.
kitaqfc/kitaqfc/CodeGenerator.cs:5222
case "__fds_current_bank":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(_runtimeFdsResidentBankAddress));
return true;__fds_farcall
Führt mit der FDS-spezifischen Schreibweise einen parameterlosen bankübergreifenden Aufruf aus. Laden und Wiederherstellen entsprechen genau __fds_overlay_farcall().
Syntax
u8 __fds_farcall(u8 bank, u16 func);Parameter
bankAusdruck für die Bank. Er wird vor dem Aufruf einmal ausgewertet; die tatsächlich geladene Bank ergibt sich jedoch aus der gelinkten Platzierung von func. Damit wird keine Adresse in einer beliebig zur Laufzeit gewählten Bank aufgerufen. Übergeben Sie einen passenden Wert wie
__bankof(func).funcName einer deklarierten Funktion ohne Parameter. Das Beispiel übergibt
u8 overlay_answer(void). Numerische Adressen, Laufzeit-Funktionszeiger und Funktionen mit erforderlichen Argumenten werden abgewiesen. Verwenden Sie bei benötigten Argumenten einen normalen Aufruf wietarget(a, b).
Rückgabewert
Acht-Bit-Ergebnis der Zielfunktion. Schlägt das Laden fehl, lässt sich aber die vorherige Bank wiederherstellen, wird die Zielfunktion nicht aufgerufen; stattdessen kommt das Ladefehlerbyte zurück. Legen Sie die Rückgabekonvention so fest, dass Fehler von normalen Ergebnissen unterscheidbar sind.
Bedingungen und Grenzen
Verwenden Sie dies mit --mapper=fds --fds-layout=fds32. Bank 0 enthält gemeinsamen Code bei $A000–$DFFF. Beim Start belegt Bank 1 den Bereich $6000–$9FFF; Banken ab 2 sind Overlays, die in dasselbe 16-KiB-Fenster geladen werden. Platzieren Sie Overlaycode oder -daten mit Direktiven wie #pragma fixed_bank 2.
Bank 1 wird aus einer separaten Boot-PRG-Datei mit 16.384 Bytes bei $6000 wiederhergestellt. Ohne Manifest sind die Standard-IDs 0 für Bank 1, 1 für gemeinsamen Code und 2 für CHR. Ein eigenes Manifest muss das Boot-PRG in 16.384-Byte-Dateien bei $6000 und $A000 aufteilen. Für Banken ab 2 lautet die Datei-ID --fds-overlay-start-id (standardmäßig 32) + Banknummer − 2.
Bei einem Aufruf in eine andere Bank wird die vorherige Bank gespeichert und vor der Rückkehr wiederhergestellt. Für verschachtelte Aufrufe gilt dasselbe. Auch nach einem teilweise fehlgeschlagenen Transfer wird eine Wiederherstellung versucht. Scheitert diese selbst, hält die Ausführung im gemeinsamen Code an, statt zu überschriebenem Code zurückzukehren. Ziele in der gemeinsamen Bank und Aufrufe innerhalb derselben Bank vermeiden unnötige Transfers.
BIOS-Aufrufe sind synchron. Diese API hält während des Ladens die Spielschleife nicht einmal pro Frame am Laufen. Verwenden Sie sie aus normalem Steuercode in der gemeinsamen Bank, nicht aus einem Interrupt-Handler. Bild- und Audiokontinuität sowie das Zusammenspiel mit dem echten BIOS müssen separat geprüft werden.
Diese Aufnahmen nutzen einen eigens geschriebenen Test für die LoadFiles-Aufrufkonvention, um kompilierten CPU-Code, Speichertransfers und jedes angezeigte Pixel zu prüfen. Echtes BIOS, Diskettentransfer-Timing, CRC und Hardwarestart werden damit nicht geprüft. KUROSAKI bildet den FDS-Controller nur teilweise nach. Eine gewöhnliche Ausführung ohne diesen Testbaustein belegt nicht dieselben Übertragungsergebnisse.
Der erste Compilerbefehl unten baut das herunterladbare Beispiel. Das Python-Prüfprogramm baut alle fünf Lektionen mit und ohne Optimierung und prüft sie anschließend in KUROSAKI. Es erstellt und entfernt einen temporären Testbaustein aus scripts/fds_abi_fixture.py; für diese Prüfungen ist kein externes BIOS nötig. Der Testbaustein ist kein BIOS für normale Spiele.
Anwendungsbeispiel
#pragma fixed_bank 0
#include "fc_fds_scene.h"
u8 choose_overlay(void){fds_result[2]++;return 2;}
#pragma fixed_bank 2
u8 overlay_answer(void){fds_result[1]++;fds_result[3]=__fds_current_bank();return 42;}
#pragma fixed_bank 0
void main(void) {
__irq_disable();fds_result[1]=0;fds_result[2]=0;
fds_result[0]=__fds_farcall(choose_overlay(),overlay_answer);
fds_result[4]=__fds_current_bank();fds_result[15]=165;
fds_call_screen();while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Mit dem eigens geschriebenen LoadFiles-ABI-Test ist RETURN VALUE 042; CALLBACK COUNT und BANK EXPR COUNT sind jeweils 001; BANK IN CALLBACK ist 002 und BANK AFTER RETURN 001. Funktion und Bankausdruck liefen jeweils einmal, und Bank 1 wurde nach dem Aufruf von Bank 2 wiederhergestellt. Dies ist keine Aufnahme eines echten Diskettentransfertests.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_farcall.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_farcall.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --fds-overlay-trim --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
python .\kitaq-docs\tools\check_fds_load_examples.pyImplementierung und Quelltext
kitaqfc/lib/fds_overlay.h:25
Same named, zero-argument dispatch as __fds_overlay_farcall; evaluate bank once.
kitaqfc/kitaqfc/CodeGenerator.cs:4074
if (string.Equals(funcName, "__fds_overlay_farcall", StringComparison.Ordinal) ||
string.Equals(funcName, "__fds_farcall", StringComparison.Ordinal))
{
// FDS overlay dispatch requires the configured FDS RAM layout and a named function target.
Expr[] farArgs = args ?? Array.Empty<Expr>();
if (farArgs.Length != 2)
{
Program.Error("error KQFC2512: {0}(bank, func) expects exactly 2 arguments.", funcName);
return;
}
string targetName;
if (!farArgs[1].Match(Tag.Name, out targetName))
{
Program.Error("error KQFC2513: {0}(bank, func) requires the 2nd argument to be a function name.", funcName);
return;
}
if (!Program.NesMapperProfile.HasFds || !Program.FdsPrgRamLayoutEnabled)
{
Program.Error("error KQFC2514: {0} requires --mapper=fds with --fds-layout=fds32.", funcName);
return;
}
// Like ordinary __farcall, this entry supplies no callback arguments.
// Validate before emitting code so a data symbol cannot become a JSR target.
if (!_functionParameters.TryGetValue(targetName, out FieldInfo[] fdsParameters))
{
Program.Error("error KQFC2610: {0} requires a declared function name; use an ordinary typed call for a function pointer.", funcName);
return;
}
if (fdsParameters.Length != 0)
{
Program.Error("error KQFC2610: {0} requires a callback with no parameters.", funcName);
return;
}
// Evaluate the bank expression exactly once, even for a safe direct call.
// The named target's linked placement determines the dispatched bank.
EmitLoadValue(farArgs[0], ctx, 1);
int callerBank = ctx == null ? 0 : ctx.Bank;
int calleeBank = GetFunctionBank(targetName);
if (ShouldUseFdsOverlayThunk(callerBank, calleeBank))
{
string directFdsThunkName = EnsureFdsOverlayThunk(targetName, calleeBank);
EmitAsm("JSR", Abs(directFdsThunkName));
RecordCallEdge(targetName, calleeBank, "fds_overlay_thunk", viaThunk: true, viaFarcall: true);
return;
}
if (calleeBank == 0 || calleeBank == callerBank)
{
EmitAsm("JSR", Abs(targetName));
RecordCallEdge(targetName, calleeBank, "direct", viaThunk: false, viaFarcall: true);
return;
}
string fdsThunkName = EnsureFdsOverlayThunk(targetName, calleeBank);
EmitAsm("JSR", Abs(fdsThunkName));
RecordCallEdge(targetName, calleeBank, "fds_overlay_thunk", viaThunk: true, viaFarcall: true);
return;
}__fds_is_bank_resident
Vergleicht die angeforderte Bank mit dem Belegungsvermerk. Das hilft zu entscheiden, ob Laden nötig ist, führt aber keinen Ladevorgang aus.
Syntax
u8 __fds_is_bank_resident(u8 bank);Parameter
bankZu vergleichende logische Banknummer. Der normale Start vermerkt 1; erzeugte Overlays verwenden 2 und höher. Bank 0 enthält gemeinsamen Code und ist nicht der Anfangswert dieses Belegungsvermerks.
Rückgabewert
1, wenn eine gültige Bank dem Vermerk entspricht, sonst 0. Bank 255 (unbekannt) liefert immer null, selbst bei einem Vermerk von 255.
Bedingungen und Grenzen
Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.
Der normale FDS-Start setzt den Belegungsvermerk auf 1. __fds_load_bank() aktualisiert ihn nach erfolgreichem Laden. Direkte Ladevorgänge über __fds_load_file() oder __fds_load_overlay() sowie fehlgeschlagene Banktransfers setzen ihn auf 255 (unbekannt). Speicherinhalte werden nicht geprüft. Verlassen Sie sich nach direkter Änderung des Fensters nicht auf diesen Vermerk. Eine eigene Resetroutine muss die Initialisierung selbst übernehmen.
Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.
Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.
Anwendungsbeispiel
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
request_overlay=0;
fds_result[0] = __fds_current_bank();
fds_result[1] = __fds_is_bank_resident(1);
fds_result[2] = __fds_is_bank_resident(2);
fds_result[3] = __fds_overlay_function_count();
if (request_overlay) {
fds_result[4]=lesson_overlay_a(); fds_result[5]=lesson_overlay_b();
}
m_init();
m_text(2,2,"FDS OVERLAY RECORDS"); m_wait();
m_text(2,5,"CURRENT BANK"); m_put(25,5,'0'+fds_result[0]); m_wait();
m_text(2,7,"BANK 1 RESIDENT"); m_put(25,7,'0'+fds_result[1]); m_wait();
m_text(2,9,"BANK 2 RESIDENT"); m_put(25,9,'0'+fds_result[2]); m_wait();
m_text(2,11,"OVERLAY FUNCTIONS"); m_put(25,11,'0'+fds_result[3]); m_wait();
m_text(2,14,"NO OVERLAY LOAD TESTED"); m_wait();
while (1) { m_wait(); }
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.


Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_residency.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_residency.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/fds_overlay.h:18
Compare a valid bank with the software resident-bank byte; FF (unknown) never matches.
kitaqfc/kitaqfc/CodeGenerator.cs:9465
EmitHelperStart("__fds_is_bank_resident");
string fdsResidentYes = NewGeneratedLabel("fds_resident_yes");
string fdsResidentNo = NewGeneratedLabel("fds_resident_no");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("CMP", Imm(0xFF));
EmitAsm("BEQ", Rel(fdsResidentNo));
EmitAsm("CMP", Mem(_runtimeFdsResidentBankAddress));
EmitAsm("BEQ", Rel(fdsResidentYes));
_assembly.Add(Expr.Make(Tag.Label, fdsResidentNo));
EmitAsm("LDA", Imm(0));
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, fdsResidentYes));
EmitAsm("LDA", Imm(1));
EmitAsm("RTS");
// Query file existence in the compiled metadata table.__fds_load_bank
Übersetzt eine logische Bank in ihre Datei-ID, lädt das Overlayfenster $6000–$9FFF und vermerkt die Bank anschließend als resident.
Syntax
u8 __fds_load_bank(u8 bank);Parameter
bankZu ladende logische Bank, 1 bis 254. Bank 0 ist gemeinsamer Code und kein ladbares Fenster; 255 ist für unbekannte Bankbelegung reserviert. Bei Banken ab 2 muss auch die berechnete Datei-ID in 0 bis 254 liegen.
Rückgabewert
Bei Erfolg null. Gibt 255 für Bank 0 oder 255, einen Datei-ID-Überlauf oder Bank 1 ohne Wiederherstellungsdatei zurück. Ein fehlgeschlagener BIOS-Transfer liefert dessen Fehlerbyte. Argumentfehler vor dem Transfer bewahren den Belegungsvermerk; fehlgeschlagene BIOS-Transfers setzen ihn auf 255 (unbekannt).
Bedingungen und Grenzen
Verwenden Sie dies mit --mapper=fds --fds-layout=fds32. Bank 0 enthält gemeinsamen Code bei $A000–$DFFF. Beim Start belegt Bank 1 den Bereich $6000–$9FFF; Banken ab 2 sind Overlays, die in dasselbe 16-KiB-Fenster geladen werden. Platzieren Sie Overlaycode oder -daten mit Direktiven wie #pragma fixed_bank 2.
Bank 1 wird aus einer separaten Boot-PRG-Datei mit 16.384 Bytes bei $6000 wiederhergestellt. Ohne Manifest sind die Standard-IDs 0 für Bank 1, 1 für gemeinsamen Code und 2 für CHR. Ein eigenes Manifest muss das Boot-PRG in 16.384-Byte-Dateien bei $6000 und $A000 aufteilen. Für Banken ab 2 lautet die Datei-ID --fds-overlay-start-id (standardmäßig 32) + Banknummer − 2.
Mit der standardmäßigen Belegungsprüfung gibt eine bereits vermerkte Bank ohne Diskettenzugriff null zurück. --fds-no-overlay-guard deaktiviert diese Abkürzung in __fds_load_bank(). Ein erfolgreicher Ladevorgang vermerkt die angeforderte Bank. Dieser Vermerk ist keine Prüfung des Speicherinhalts.
Das direkte Laden einer anderen Bank aus Code im Fenster $6000–$9FFF kann die laufende Funktion oder deren Rücksprungadresse überschreiben. Das Beispiel legt Aufrufer und Anzeigecode in #pragma fixed_bank 0 ab. Um eine Funktion in einer anderen Bank aufzurufen und zurückzukehren, verwenden Sie __fds_overlay_farcall() oder einen normalen typisierten Funktionsaufruf.
BIOS-Aufrufe sind synchron. Diese API hält während des Ladens die Spielschleife nicht einmal pro Frame am Laufen. Verwenden Sie sie aus normalem Steuercode in der gemeinsamen Bank, nicht aus einem Interrupt-Handler. Bild- und Audiokontinuität sowie das Zusammenspiel mit dem echten BIOS müssen separat geprüft werden.
Diese Aufnahmen nutzen einen eigens geschriebenen Test für die LoadFiles-Aufrufkonvention, um kompilierten CPU-Code, Speichertransfers und jedes angezeigte Pixel zu prüfen. Echtes BIOS, Diskettentransfer-Timing, CRC und Hardwarestart werden damit nicht geprüft. KUROSAKI bildet den FDS-Controller nur teilweise nach. Eine gewöhnliche Ausführung ohne diesen Testbaustein belegt nicht dieselben Übertragungsergebnisse.
Der erste Compilerbefehl unten baut das herunterladbare Beispiel. Das Python-Prüfprogramm baut alle fünf Lektionen mit und ohne Optimierung und prüft sie anschließend in KUROSAKI. Es erstellt und entfernt einen temporären Testbaustein aus scripts/fds_abi_fixture.py; für diese Prüfungen ist kein externes BIOS nötig. Der Testbaustein ist kein BIOS für normale Spiele.
Anwendungsbeispiel
#pragma fixed_bank 0
#include "fc_fds_scene.h"
#pragma fixed_bank 2
__prg_rom u8 bank_payload[4]={11,22,33,44};
#pragma fixed_bank 0
void main(void) {
__irq_disable();
fds_result[0]=__fds_load_bank(2);
fds_result[1]=__fds_current_bank();
fds_result[2]=__fds_require_bank(2);
fds_result[3]=bank_payload[0];fds_result[4]=bank_payload[3];
fds_result[5]=nes_fds_load_bank(1);
fds_result[6]=__fds_current_bank();
fds_result[7]=__fds_require_bank(1);
fds_result[8]=__fds_load_bank(0);
fds_result[9]=__fds_load_bank(255);
fds_result[15]=165;
m_init();m_text(2,2,"FDS DATA WINDOW");m_wait();
m_text(2,5,"LOAD BANK 2 STATUS");fds_number(25,5,fds_result[0]);m_wait();
m_text(2,7,"FIRST / LAST BYTE");fds_number(21,7,fds_result[3]);fds_number(25,7,fds_result[4]);m_wait();
m_text(2,9,"REQUIRE SAME BANK");fds_number(25,9,fds_result[2]);m_wait();
m_text(2,11,"RESTORE BANK 1");fds_number(25,11,fds_result[5]);m_wait();
m_text(2,13,"RESIDENT BANK");fds_number(25,13,fds_result[6]);m_wait();
m_text(2,16,"INVALID BANKS 0 / 255");m_wait();
fds_number(2,18,fds_result[8]);fds_number(8,18,fds_result[9]);m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Mit dem eigens geschriebenen LoadFiles-ABI-Test sind LOAD BANK 2 STATUS, REQUIRE SAME BANK und RESTORE BANK 1 jeweils 000. FIRST / LAST BYTE zeigt 011 und 044 aus den vier übertragenen Bytes; RESIDENT BANK ist nach der Wiederherstellung 001. Die ungültigen Banken 0 und 255 liefern jeweils 255. Das Anfordern derselben Bank löst keinen weiteren Ladevorgang aus: Die geprüften Dateianforderungen sind 32 und danach 0.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_bank_load.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_bank_load.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --fds-overlay-trim --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
python .\kitaq-docs\tools\check_fds_load_examples.pyImplementierung und Quelltext
kitaqfc/lib/fds_overlay.h:11
Load logical bank 1 from its separate boot file, or bank 2+ from overlay-start-id+(bank-2). Return zero on success, FF for invalid/unavailable mappings, or the BIOS error. A failed BIOS transfer marks residency FF (unknown); a matching valid guard can skip loading.
kitaqfc/kitaqfc/CodeGenerator.cs:9394
EmitHelperStart("__fds_load_bank");
string fdsLoadBankLoad = NewGeneratedLabel("fds_load_bank_load");
string fdsLoadBankOverlay = NewGeneratedLabel("fds_load_bank_overlay");
string fdsLoadBankCall = NewGeneratedLabel("fds_load_bank_call");
string fdsLoadBankDone = NewGeneratedLabel("fds_load_bank_done");
string fdsLoadBankPopInvalid = NewGeneratedLabel("fds_load_bank_pop_invalid");
string fdsLoadBankInvalid = NewGeneratedLabel("fds_load_bank_invalid");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("CMP", Imm(1));
EmitAsm("BCC", Rel(fdsLoadBankInvalid));
EmitAsm("CMP", Imm(0xFF));
EmitAsm("BEQ", Rel(fdsLoadBankInvalid));
if (Program.FdsOverlayResidencyGuardEnabled)
{
EmitAsm("CMP", Mem(_runtimeFdsResidentBankAddress));
EmitAsm("BNE", Rel(fdsLoadBankLoad));
EmitAsm("LDA", Imm(0));
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, fdsLoadBankLoad));
}
EmitAsm("PHA");
EmitAsm("CMP", Imm(1));
EmitAsm("BNE", Rel(fdsLoadBankOverlay));
EmitAsm("LDA", Abs("__kq_fds_boot_bank_file_id"));
EmitAsm("CMP", Imm(0xFF));
EmitAsm("BEQ", Rel(fdsLoadBankPopInvalid));
EmitAsm("JMP", Abs(fdsLoadBankCall));
_assembly.Add(Expr.Make(Tag.Label, fdsLoadBankOverlay));
EmitAsm("SEC");
EmitAsm("SBC", Imm(2));
EmitAsm("CLC");
EmitAsm("ADC", Imm(Program.FdsOverlayStartId & 0xFF));
EmitAsm("BCS", Rel(fdsLoadBankPopInvalid));
EmitAsm("CMP", Imm(0xFF));
EmitAsm("BEQ", Rel(fdsLoadBankPopInvalid));
_assembly.Add(Expr.Make(Tag.Label, fdsLoadBankCall));
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__fds_load_overlay"));
EmitAsm("TAX");
EmitAsm("PLA");
EmitAsm("CPX", Imm(0));
EmitAsm("BEQ", Rel(fdsLoadBankDone));
// A failed BIOS transfer may have overwritten part of the window.
// Mark it unknown so a subsequent restore cannot be skipped by the guard.
EmitAsm("LDA", Imm(0xFF));
_assembly.Add(Expr.Make(Tag.Label, fdsLoadBankDone));
EmitAsm("STA", Mem(_runtimeFdsResidentBankAddress));
EmitAsm("STA", Mem(_runtimeCurrentBankAddress));
EmitAsm("TXA");
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, fdsLoadBankPopInvalid));
EmitAsm("PLA");
_assembly.Add(Expr.Make(Tag.Label, fdsLoadBankInvalid));
EmitAsm("LDA", Imm(0xFF));
EmitAsm("RTS");
// Return success for the recorded resident bank, otherwise dispatch a bank load.__fds_load_overlay
Ruft BIOS LoadFiles mit einer Disketten-Datei-ID statt einer logischen Banknummer auf. Der Dateiheader bestimmt das Ziel.
Syntax
u8 __fds_load_overlay(u8 id);Parameter
idDisketten-Datei-ID von 0 bis 254. Übergeben Sie nicht 255; dieser Wert beendet die BIOS-Dateiliste. Im Standardlayout enthält ID 32 die Bank 2.
Rückgabewert
BIOS-Fehlerbyte; null bedeutet Erfolg. Nach Erfolg wie Fehlschlag wird die Belegung mit 255 (unbekannt) vermerkt. Eine Datei-ID allein legt keine Zuordnung zu einer logischen Bank fest.
Bedingungen und Grenzen
Verwenden Sie dies mit --mapper=fds --fds-layout=fds32. Bank 0 enthält gemeinsamen Code bei $A000–$DFFF. Beim Start belegt Bank 1 den Bereich $6000–$9FFF; Banken ab 2 sind Overlays, die in dasselbe 16-KiB-Fenster geladen werden. Platzieren Sie Overlaycode oder -daten mit Direktiven wie #pragma fixed_bank 2.
Erstellt eine Liste mit der angeforderten ID und dem Abschlusswert 255. Prüfen Sie das Ziel im Manifest, damit die Datei weder gemeinsamen noch laufenden Code überschreibt. Verwenden Sie für logische Overlays vorzugsweise __fds_load_bank(), das die Belegung verwaltet.
Das direkte Laden einer anderen Bank aus Code im Fenster $6000–$9FFF kann die laufende Funktion oder deren Rücksprungadresse überschreiben. Das Beispiel legt Aufrufer und Anzeigecode in #pragma fixed_bank 0 ab. Um eine Funktion in einer anderen Bank aufzurufen und zurückzukehren, verwenden Sie __fds_overlay_farcall() oder einen normalen typisierten Funktionsaufruf.
BIOS-Aufrufe sind synchron. Diese API hält während des Ladens die Spielschleife nicht einmal pro Frame am Laufen. Verwenden Sie sie aus normalem Steuercode in der gemeinsamen Bank, nicht aus einem Interrupt-Handler. Bild- und Audiokontinuität sowie das Zusammenspiel mit dem echten BIOS müssen separat geprüft werden.
Diese Aufnahmen nutzen einen eigens geschriebenen Test für die LoadFiles-Aufrufkonvention, um kompilierten CPU-Code, Speichertransfers und jedes angezeigte Pixel zu prüfen. Echtes BIOS, Diskettentransfer-Timing, CRC und Hardwarestart werden damit nicht geprüft. KUROSAKI bildet den FDS-Controller nur teilweise nach. Eine gewöhnliche Ausführung ohne diesen Testbaustein belegt nicht dieselben Übertragungsergebnisse.
Der erste Compilerbefehl unten baut das herunterladbare Beispiel. Das Python-Prüfprogramm baut alle fünf Lektionen mit und ohne Optimierung und prüft sie anschließend in KUROSAKI. Es erstellt und entfernt einen temporären Testbaustein aus scripts/fds_abi_fixture.py; für diese Prüfungen ist kein externes BIOS nötig. Der Testbaustein ist kein BIOS für normale Spiele.
Anwendungsbeispiel
#pragma fixed_bank 0
#include "fc_fds_scene.h"
#pragma fixed_bank 2
__prg_rom u8 raw_payload[4]={11,22,33,44};
#pragma fixed_bank 0
void main(void) {
__irq_disable();
fds_result[0]=__fds_load_overlay(32);
fds_result[1]=__fds_current_bank();
fds_result[2]=__fds_is_bank_resident(1);
fds_result[3]=raw_payload[0];fds_result[4]=raw_payload[3];
fds_result[5]=__fds_require_bank(1);
fds_result[6]=__fds_current_bank();
fds_result[15]=165;
m_init();m_text(2,2,"FDS RAW FILE-ID LOAD");m_wait();
m_text(2,5,"FILE 32 LOAD STATUS");fds_number(25,5,fds_result[0]);m_wait();
m_text(2,7,"FIRST / LAST BYTE");fds_number(21,7,fds_result[3]);fds_number(25,7,fds_result[4]);m_wait();
m_text(2,9,"UNKNOWN BANK RECORD");fds_number(25,9,fds_result[1]);m_wait();
m_text(2,11,"IS BANK 1 RESIDENT");fds_number(25,11,fds_result[2]);m_wait();
m_text(2,13,"REQUIRE BANK 1");fds_number(25,13,fds_result[5]);m_wait();
m_text(2,15,"RESIDENT AFTER RELOAD");fds_number(25,15,fds_result[6]);m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Mit dem eigens geschriebenen LoadFiles-ABI-Test ist FILE 32 LOAD STATUS 000; das erste und letzte Byte der übertragenen Daten sind 011 und 044. Nach dem direkten Laden ist UNKNOWN BANK RECORD 255 und IS BANK 1 RESIDENT 000. REQUIRE BANK 1 gelingt mit 000, danach ist RESIDENT AFTER RELOAD 001. Dies verdeutlicht den Unterschied zwischen Laden per Datei-ID und Verwaltung einer logischen Bank.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_raw_overlay.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_raw_overlay.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --fds-overlay-trim --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
python .\kitaq-docs\tools\check_fds_load_examples.pyImplementierung und Quelltext
kitaqfc/lib/fds_overlay.h:7
Load disk file id through BIOS, return its error byte and mark bank residency FF (unknown).
kitaqfc/kitaqfc/CodeGenerator.cs:9375
EmitHelperStart("__fds_load_overlay");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeFdsLoadListAddress));
EmitAsm("LDA", Imm(0xFF));
EmitAsm("STA", Mem(_runtimeFdsLoadListAddress + 1));
EmitAsm("JSR", Mem(0xE1F8));
_assembly.Add(Expr.Make(Tag.Word, "__kq_fds_disk_id_wildcard"));
_assembly.Add(Expr.Make(Tag.Word, string.Format("${0:X4}", _runtimeFdsLoadListAddress)));
// A raw file load can replace any part of the switchable window.
// Only the bank-aware wrapper may establish a known resident bank.
EmitAsm("TAX");
EmitAsm("LDA", Imm(0xFF));
EmitAsm("STA", Mem(_runtimeFdsResidentBankAddress));
EmitAsm("STA", Mem(_runtimeCurrentBankAddress));
EmitAsm("TXA");
EmitAsm("RTS");
// Restore bank 1 from its separate boot file; map banks 2+ to overlay
// IDs. Keep the requested bank on the CPU stack across the BIOS call.__fds_overlay_farcall
Ruft eine parameterlose Funktion in einem Overlay auf und stellt vor der Rückkehr die vorherige Bank wieder her.
Syntax
u8 __fds_overlay_farcall(u8 bank, u16 func);Parameter
bankAusdruck für die Bank. Er wird vor dem Aufruf einmal ausgewertet; die tatsächlich geladene Bank ergibt sich jedoch aus der gelinkten Platzierung von func. Damit wird keine Adresse in einer beliebig zur Laufzeit gewählten Bank aufgerufen. Übergeben Sie einen passenden Wert wie
__bankof(func).funcName einer deklarierten Funktion ohne Parameter. Das Beispiel übergibt
u8 overlay_answer(void). Numerische Adressen, Laufzeit-Funktionszeiger und Funktionen mit erforderlichen Argumenten werden abgewiesen. Verwenden Sie bei benötigten Argumenten einen normalen Aufruf wietarget(a, b).
Rückgabewert
Acht-Bit-Ergebnis der Zielfunktion. Schlägt das Laden fehl, lässt sich aber die vorherige Bank wiederherstellen, wird die Zielfunktion nicht aufgerufen; stattdessen kommt das Ladefehlerbyte zurück. Legen Sie die Rückgabekonvention so fest, dass Fehler von normalen Ergebnissen unterscheidbar sind.
Bedingungen und Grenzen
Verwenden Sie dies mit --mapper=fds --fds-layout=fds32. Bank 0 enthält gemeinsamen Code bei $A000–$DFFF. Beim Start belegt Bank 1 den Bereich $6000–$9FFF; Banken ab 2 sind Overlays, die in dasselbe 16-KiB-Fenster geladen werden. Platzieren Sie Overlaycode oder -daten mit Direktiven wie #pragma fixed_bank 2.
Bank 1 wird aus einer separaten Boot-PRG-Datei mit 16.384 Bytes bei $6000 wiederhergestellt. Ohne Manifest sind die Standard-IDs 0 für Bank 1, 1 für gemeinsamen Code und 2 für CHR. Ein eigenes Manifest muss das Boot-PRG in 16.384-Byte-Dateien bei $6000 und $A000 aufteilen. Für Banken ab 2 lautet die Datei-ID --fds-overlay-start-id (standardmäßig 32) + Banknummer − 2.
Bei einem Aufruf in eine andere Bank wird die vorherige Bank gespeichert und vor der Rückkehr wiederhergestellt. Für verschachtelte Aufrufe gilt dasselbe. Auch nach einem teilweise fehlgeschlagenen Transfer wird eine Wiederherstellung versucht. Scheitert diese selbst, hält die Ausführung im gemeinsamen Code an, statt zu überschriebenem Code zurückzukehren. Ziele in der gemeinsamen Bank und Aufrufe innerhalb derselben Bank vermeiden unnötige Transfers.
BIOS-Aufrufe sind synchron. Diese API hält während des Ladens die Spielschleife nicht einmal pro Frame am Laufen. Verwenden Sie sie aus normalem Steuercode in der gemeinsamen Bank, nicht aus einem Interrupt-Handler. Bild- und Audiokontinuität sowie das Zusammenspiel mit dem echten BIOS müssen separat geprüft werden.
Diese Aufnahmen nutzen einen eigens geschriebenen Test für die LoadFiles-Aufrufkonvention, um kompilierten CPU-Code, Speichertransfers und jedes angezeigte Pixel zu prüfen. Echtes BIOS, Diskettentransfer-Timing, CRC und Hardwarestart werden damit nicht geprüft. KUROSAKI bildet den FDS-Controller nur teilweise nach. Eine gewöhnliche Ausführung ohne diesen Testbaustein belegt nicht dieselben Übertragungsergebnisse.
Der erste Compilerbefehl unten baut das herunterladbare Beispiel. Das Python-Prüfprogramm baut alle fünf Lektionen mit und ohne Optimierung und prüft sie anschließend in KUROSAKI. Es erstellt und entfernt einen temporären Testbaustein aus scripts/fds_abi_fixture.py; für diese Prüfungen ist kein externes BIOS nötig. Der Testbaustein ist kein BIOS für normale Spiele.
Anwendungsbeispiel
#pragma fixed_bank 0
#include "fc_fds_scene.h"
u8 choose_overlay(void){fds_result[2]++;return 2;}
#pragma fixed_bank 2
u8 overlay_answer(void){fds_result[1]++;fds_result[3]=__fds_current_bank();return 42;}
#pragma fixed_bank 0
void main(void) {
__irq_disable();fds_result[1]=0;fds_result[2]=0;
fds_result[0]=__fds_overlay_farcall(choose_overlay(),overlay_answer);
fds_result[4]=__fds_current_bank();fds_result[15]=165;
fds_call_screen();while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Mit dem eigens geschriebenen LoadFiles-ABI-Test ist RETURN VALUE 042; CALLBACK COUNT und BANK EXPR COUNT sind jeweils 001; BANK IN CALLBACK ist 002 und BANK AFTER RETURN 001. Funktion und Bankausdruck liefen jeweils einmal, und Bank 1 wurde nach dem Aufruf von Bank 2 wiederhergestellt. Dies ist keine Aufnahme eines echten Diskettentransfertests.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_overlay_call.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_overlay_call.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --fds-overlay-trim --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
python .\kitaq-docs\tools\check_fds_load_examples.pyImplementierung und Quelltext
kitaqfc/lib/fds_overlay.h:23
Evaluate bank once and call the named zero-argument function using its linked bank. Restore the caller window before returning; restoration failure stops in common code.
kitaqfc/kitaqfc/CodeGenerator.cs:4074
if (string.Equals(funcName, "__fds_overlay_farcall", StringComparison.Ordinal) ||
string.Equals(funcName, "__fds_farcall", StringComparison.Ordinal))
{
// FDS overlay dispatch requires the configured FDS RAM layout and a named function target.
Expr[] farArgs = args ?? Array.Empty<Expr>();
if (farArgs.Length != 2)
{
Program.Error("error KQFC2512: {0}(bank, func) expects exactly 2 arguments.", funcName);
return;
}
string targetName;
if (!farArgs[1].Match(Tag.Name, out targetName))
{
Program.Error("error KQFC2513: {0}(bank, func) requires the 2nd argument to be a function name.", funcName);
return;
}
if (!Program.NesMapperProfile.HasFds || !Program.FdsPrgRamLayoutEnabled)
{
Program.Error("error KQFC2514: {0} requires --mapper=fds with --fds-layout=fds32.", funcName);
return;
}
// Like ordinary __farcall, this entry supplies no callback arguments.
// Validate before emitting code so a data symbol cannot become a JSR target.
if (!_functionParameters.TryGetValue(targetName, out FieldInfo[] fdsParameters))
{
Program.Error("error KQFC2610: {0} requires a declared function name; use an ordinary typed call for a function pointer.", funcName);
return;
}
if (fdsParameters.Length != 0)
{
Program.Error("error KQFC2610: {0} requires a callback with no parameters.", funcName);
return;
}
// Evaluate the bank expression exactly once, even for a safe direct call.
// The named target's linked placement determines the dispatched bank.
EmitLoadValue(farArgs[0], ctx, 1);
int callerBank = ctx == null ? 0 : ctx.Bank;
int calleeBank = GetFunctionBank(targetName);
if (ShouldUseFdsOverlayThunk(callerBank, calleeBank))
{
string directFdsThunkName = EnsureFdsOverlayThunk(targetName, calleeBank);
EmitAsm("JSR", Abs(directFdsThunkName));
RecordCallEdge(targetName, calleeBank, "fds_overlay_thunk", viaThunk: true, viaFarcall: true);
return;
}
if (calleeBank == 0 || calleeBank == callerBank)
{
EmitAsm("JSR", Abs(targetName));
RecordCallEdge(targetName, calleeBank, "direct", viaThunk: false, viaFarcall: true);
return;
}
string fdsThunkName = EnsureFdsOverlayThunk(targetName, calleeBank);
EmitAsm("JSR", Abs(fdsThunkName));
RecordCallEdge(targetName, calleeBank, "fds_overlay_thunk", viaThunk: true, viaFarcall: true);
return;
}__fds_overlay_function_count
Gibt die Anzahl der vom Linker in Overlaybanken ab 2 platzierten Funktionen zurück. Das ist weder die Anzahl residenter Funktionen noch die Dateianzahl.
Syntax
u8 __fds_overlay_function_count(void);Rückgabewert
Führendes Anzahlbyte der Tabelle von 0 bis 255. Null, wenn es keine Overlayfunktionen gibt oder --fds-no-overlay-table verwendet wird. Mehr als 255 Einträge werden mit KQFC2511 abgewiesen.
Bedingungen und Grenzen
Bauen Sie mit --mapper=fds. Bei einem Cartridge-Mapper wird der Aufruf einer FDS-exklusiven Funktion außer __fds_available mit KQFC2420 abgewiesen.
Die Codegenerierung legt einen Null-Platzhalter an. Der native FDS-Linker ersetzt ihn nach Bank- und Adressplatzierung durch die Tabelle. Das Beispiel legt zwei Funktionen in Bank 2 ab, ohne sie aufzurufen. Die Anfangsdaten bei --fds-layout=legacy sind keine vollständige Zählung der Overlayfunktionen.
Die Aufnahme nutzt KUROSAKIs direktes Laden der Disketten-Bootdateien ins RAM. Das Beispiel ruft keine BIOS-Routine auf und prüft weder Dateitransfers noch Diskettenwechsel oder Hardwarestart. Die leere Testfirmware ist kein Ersatz-BIOS. Das Beispiel aktualisiert die Anzeige per NMI und deaktiviert maskierbare IRQs, die diese Abfragen nicht benötigen.
Das Buildbeispiel erzeugt eine .fds-Datei mit nativem Layout ohne zusätzliche Bootdateien für den Genehmigungsbildschirm. Diese Konfiguration prüft KUROSAKIs Direktstart; das Startverfahren einer echten Diskette muss separat validiert werden.
Anwendungsbeispiel
void main(void) {
// These BIOS-free queries need NMI for display, but no maskable IRQ.
__irq_disable();
request_overlay=0;
fds_result[0] = __fds_current_bank();
fds_result[1] = __fds_is_bank_resident(1);
fds_result[2] = __fds_is_bank_resident(2);
fds_result[3] = __fds_overlay_function_count();
if (request_overlay) {
fds_result[4]=lesson_overlay_a(); fds_result[5]=lesson_overlay_b();
}
m_init();
m_text(2,2,"FDS OVERLAY RECORDS"); m_wait();
m_text(2,5,"CURRENT BANK"); m_put(25,5,'0'+fds_result[0]); m_wait();
m_text(2,7,"BANK 1 RESIDENT"); m_put(25,7,'0'+fds_result[1]); m_wait();
m_text(2,9,"BANK 2 RESIDENT"); m_put(25,9,'0'+fds_result[2]); m_wait();
m_text(2,11,"OVERLAY FUNCTIONS"); m_put(25,11,'0'+fds_result[3]); m_wait();
m_text(2,14,"NO OVERLAY LOAD TESTED"); m_wait();
while (1) { m_wait(); }
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
CURRENT BANK ist 1, BANK 1 RESIDENT ist 1, BANK 2 RESIDENT ist 0 und OVERLAY FUNCTIONS ist 2. Zwei in Bank 2 abgelegte Funktionen machen diese Bank beim Start noch nicht resident. Im Prüfbuild mit --fds-no-overlay-table ändert sich nur der letzte Wert auf null.


Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_residency.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_residency.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/fds_overlay.h:20
Return the linker-generated overlay-function count; zero if the table is disabled.
kitaqfc/kitaqfc/CodeGenerator.cs:5229
case "__fds_overlay_function_count":
if (!Need(0)) return true;
EmitAsm("LDA", Abs("__kq_fds_overlay_function_table"));
return true;__fds_require_bank
Lädt eine Bank nur, wenn sie noch nicht als resident vermerkt ist. Ein Aufruf vor dem Ressourcenzugriff vermeidet unnötige Transfers, wenn die Daten bereits verfügbar sind.
Syntax
u8 __fds_require_bank(u8 bank);Parameter
bankZu ladende logische Bank, 1 bis 254. Bank 0 ist gemeinsamer Code und kein ladbares Fenster; 255 ist für unbekannte Bankbelegung reserviert. Bei Banken ab 2 muss auch die berechnete Datei-ID in 0 bis 254 liegen.
Rückgabewert
Bei Erfolg null. Gibt 255 für Bank 0 oder 255, einen Datei-ID-Überlauf oder Bank 1 ohne Wiederherstellungsdatei zurück. Ein fehlgeschlagener BIOS-Transfer liefert dessen Fehlerbyte. Argumentfehler vor dem Transfer bewahren den Belegungsvermerk; fehlgeschlagene BIOS-Transfers setzen ihn auf 255 (unbekannt).
Bedingungen und Grenzen
Verwenden Sie dies mit --mapper=fds --fds-layout=fds32. Bank 0 enthält gemeinsamen Code bei $A000–$DFFF. Beim Start belegt Bank 1 den Bereich $6000–$9FFF; Banken ab 2 sind Overlays, die in dasselbe 16-KiB-Fenster geladen werden. Platzieren Sie Overlaycode oder -daten mit Direktiven wie #pragma fixed_bank 2.
Eine gültige Bank, die dem Vermerk entspricht, liefert null; andernfalls wird __fds_load_bank() aufgerufen. Der anfängliche Vergleich bleibt auch mit --fds-no-overlay-guard aktiv. Bank 255 gilt niemals als resident, selbst wenn der Vermerk 255 enthält.
Bank 1 wird aus einer separaten Boot-PRG-Datei mit 16.384 Bytes bei $6000 wiederhergestellt. Ohne Manifest sind die Standard-IDs 0 für Bank 1, 1 für gemeinsamen Code und 2 für CHR. Ein eigenes Manifest muss das Boot-PRG in 16.384-Byte-Dateien bei $6000 und $A000 aufteilen. Für Banken ab 2 lautet die Datei-ID --fds-overlay-start-id (standardmäßig 32) + Banknummer − 2.
Das direkte Laden einer anderen Bank aus Code im Fenster $6000–$9FFF kann die laufende Funktion oder deren Rücksprungadresse überschreiben. Das Beispiel legt Aufrufer und Anzeigecode in #pragma fixed_bank 0 ab. Um eine Funktion in einer anderen Bank aufzurufen und zurückzukehren, verwenden Sie __fds_overlay_farcall() oder einen normalen typisierten Funktionsaufruf.
BIOS-Aufrufe sind synchron. Diese API hält während des Ladens die Spielschleife nicht einmal pro Frame am Laufen. Verwenden Sie sie aus normalem Steuercode in der gemeinsamen Bank, nicht aus einem Interrupt-Handler. Bild- und Audiokontinuität sowie das Zusammenspiel mit dem echten BIOS müssen separat geprüft werden.
Diese Aufnahmen nutzen einen eigens geschriebenen Test für die LoadFiles-Aufrufkonvention, um kompilierten CPU-Code, Speichertransfers und jedes angezeigte Pixel zu prüfen. Echtes BIOS, Diskettentransfer-Timing, CRC und Hardwarestart werden damit nicht geprüft. KUROSAKI bildet den FDS-Controller nur teilweise nach. Eine gewöhnliche Ausführung ohne diesen Testbaustein belegt nicht dieselben Übertragungsergebnisse.
Der erste Compilerbefehl unten baut das herunterladbare Beispiel. Das Python-Prüfprogramm baut alle fünf Lektionen mit und ohne Optimierung und prüft sie anschließend in KUROSAKI. Es erstellt und entfernt einen temporären Testbaustein aus scripts/fds_abi_fixture.py; für diese Prüfungen ist kein externes BIOS nötig. Der Testbaustein ist kein BIOS für normale Spiele.
Anwendungsbeispiel
#pragma fixed_bank 0
#include "fc_fds_scene.h"
#pragma fixed_bank 2
__prg_rom u8 bank_payload[4]={11,22,33,44};
#pragma fixed_bank 0
void main(void) {
__irq_disable();
fds_result[0]=__fds_load_bank(2);
fds_result[1]=__fds_current_bank();
fds_result[2]=__fds_require_bank(2);
fds_result[3]=bank_payload[0];fds_result[4]=bank_payload[3];
fds_result[5]=nes_fds_load_bank(1);
fds_result[6]=__fds_current_bank();
fds_result[7]=__fds_require_bank(1);
fds_result[8]=__fds_load_bank(0);
fds_result[9]=__fds_load_bank(255);
fds_result[15]=165;
m_init();m_text(2,2,"FDS DATA WINDOW");m_wait();
m_text(2,5,"LOAD BANK 2 STATUS");fds_number(25,5,fds_result[0]);m_wait();
m_text(2,7,"FIRST / LAST BYTE");fds_number(21,7,fds_result[3]);fds_number(25,7,fds_result[4]);m_wait();
m_text(2,9,"REQUIRE SAME BANK");fds_number(25,9,fds_result[2]);m_wait();
m_text(2,11,"RESTORE BANK 1");fds_number(25,11,fds_result[5]);m_wait();
m_text(2,13,"RESIDENT BANK");fds_number(25,13,fds_result[6]);m_wait();
m_text(2,16,"INVALID BANKS 0 / 255");m_wait();
fds_number(2,18,fds_result[8]);fds_number(8,18,fds_result[9]);m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Mit dem eigens geschriebenen LoadFiles-ABI-Test sind LOAD BANK 2 STATUS, REQUIRE SAME BANK und RESTORE BANK 1 jeweils 000. FIRST / LAST BYTE zeigt 011 und 044 aus den vier übertragenen Bytes; RESIDENT BANK ist nach der Wiederherstellung 001. Die ungültigen Banken 0 und 255 liefern jeweils 255. Das Anfordern derselben Bank löst keinen weiteren Ladevorgang aus: Die geprüften Dateianforderungen sind 32 und danach 0.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\fds_bank_load.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fds_bank_load.fds --mapper=fds --fds-layout=fds32 --fds-no-license-bypass --fds-overlay-trim --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
python .\kitaq-docs\tools\check_fds_load_examples.pyImplementierung und Quelltext
kitaqfc/lib/fds_overlay.h:13
Return zero when the software residency record matches, otherwise call __fds_load_bank.
kitaqfc/kitaqfc/CodeGenerator.cs:9451
EmitHelperStart("__fds_require_bank");
string fdsRequireLoad = NewGeneratedLabel("fds_require_load");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("CMP", Imm(0xFF));
EmitAsm("BEQ", Rel(fdsRequireLoad));
EmitAsm("CMP", Mem(_runtimeFdsResidentBankAddress));
EmitAsm("BNE", Rel(fdsRequireLoad));
EmitAsm("LDA", Imm(0));
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, fdsRequireLoad));
EmitAsm("JSR", Abs("__fds_load_bank"));
EmitAsm("RTS");
// Compare the requested bank with the software residency record and return zero or one.fds_sound.h — FDS-Wavetable-Audio
Deklariert Compiler-Intrinsics für 64-stufige Wellenform, Modulationstabelle, Tonhöhe, Lautstärke und Hüllkurven. KITAQFC erzeugt ihre Implementierungen.
Erstellen Sie mit --mapper=fds ein .fds-Abbild. Diese Lektion verwendet Sound ohne Datenträger-I/O. Die Aufnahme nutzt KUROSAKIs direkten RAM-Start und Audiomodell. Die leere Testfirmware implementiert keine BIOS-Routinen und wird nicht verteilt. Normaler Diskettenstart und echte Hardware benötigen eine passende Startumgebung.
__fds_env_set
Schreibt die drei rohen Argumente der Reihe nach nach $4080, $4084 und $408A.
Syntax
void __fds_env_set(u8 speed, u8 gain, u8 mode);Parameter
speedTrotz des Parameternamens ist dies der rohe $4080-Wert: Bit 7 direkte Lautstärke, Bit 6 steigende Hüllkurve.
gainRoher Modulationshüllkurvenwert für $4084.
modeIn $408A geschriebene Hüllkurven-Hauptgeschwindigkeit.
Rückgabewert
Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.
Bedingungen und Grenzen
Erstellen Sie mit --mapper=fds ein .fds-Abbild. Diese Lektion verwendet Sound ohne Datenträger-I/O. Die Aufnahme nutzt KUROSAKIs direkten RAM-Start und Audiomodell. Die leere Testfirmware implementiert keine BIOS-Routinen und wird nicht verteilt. Normaler Diskettenstart und echte Hardware benötigen eine passende Startumgebung.
Anwendungsbeispiel
// Mapper 20 sound lesson: original 64-step triangle and two sustained pitches.
#include "fc_sound_example.h"
#include "fds_sound.h"
#include "nes_game.h"
__location(0x4087) u8 mod_control;
__location(0x4085) u8 mod_counter;
u8 triangle[64]; u8 modulation[32];
void main(void) {
u8 i;
sound_begin("FDS WAVE AND VOLUME");
for(i=0;i<32;i++) { triangle[i]=(u8)(i*2); triangle[63-i]=(u8)(i*2); modulation[i]=0; }
__fds_sound_enable();
// Halt/reset modulation before writing the 32 values (each writes two slots).
mod_control=0x80; mod_counter=0;
__fds_mod_load(modulation);
__fds_wave_load(triangle);
sound_result[0]=*((u8*)0x4040); sound_result[1]=*((u8*)0x405F);
__fds_freq_set(1031);
__fds_volume_set(24);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
// This macro forwards the same 64-byte load. Use raw envelope registers.
nes_fds_wave_load(triangle);
__fds_env_set(0x98,0x80,0);
__fds_freq_set(1545);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
sound_end();
}Erwartetes Ergebnis
Ein eigenes 64-stufiges Dreieck spielt bei etwa 440 Hz, pausiert, spielt bei etwa 660 Hz und stoppt. Die zweite Note nutzt nes_fds_wave_load und rohe Hüllkurveneinstellungen. Geprüft werden Audiomodell und Direktstart, nicht BIOS-gesteuerter Diskettenstart.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_fds.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-fds.fds --mapper=fds --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/fds_sound.h:18
Write the three raw arguments to 4080, 4084 and 408A in that order. The legacy parameter names do not describe three independently packed envelope fields.
kitaqfc/kitaqfc/CodeGenerator.cs:5207
case "__fds_env_set":
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1}, "__fds_env_set");
// File helpers use byte identifiers and word memory addresses/lengths.
EmitHelperStart("__fds_env_set");
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("STA", Mem(0x4080));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("STA", Mem(0x4084));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("STA", Mem(0x408A));
EmitAsm("RTS");__fds_freq_set
Schreibt die 12-Bit-Frequenz nach $4082/$4083 und löscht die oberen Steuerbits von $4083.
Syntax
void __fds_freq_set(u16 freq);Parameter
freqFDS-Frequenzregisterwert 0..4095. Obere Steuerbits werden gelöscht.
Rückgabewert
Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.
Bedingungen und Grenzen
Erstellen Sie mit --mapper=fds ein .fds-Abbild. Diese Lektion verwendet Sound ohne Datenträger-I/O. Die Aufnahme nutzt KUROSAKIs direkten RAM-Start und Audiomodell. Die leere Testfirmware implementiert keine BIOS-Routinen und wird nicht verteilt. Normaler Diskettenstart und echte Hardware benötigen eine passende Startumgebung.
Anwendungsbeispiel
// Mapper 20 sound lesson: original 64-step triangle and two sustained pitches.
#include "fc_sound_example.h"
#include "fds_sound.h"
#include "nes_game.h"
__location(0x4087) u8 mod_control;
__location(0x4085) u8 mod_counter;
u8 triangle[64]; u8 modulation[32];
void main(void) {
u8 i;
sound_begin("FDS WAVE AND VOLUME");
for(i=0;i<32;i++) { triangle[i]=(u8)(i*2); triangle[63-i]=(u8)(i*2); modulation[i]=0; }
__fds_sound_enable();
// Halt/reset modulation before writing the 32 values (each writes two slots).
mod_control=0x80; mod_counter=0;
__fds_mod_load(modulation);
__fds_wave_load(triangle);
sound_result[0]=*((u8*)0x4040); sound_result[1]=*((u8*)0x405F);
__fds_freq_set(1031);
__fds_volume_set(24);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
// This macro forwards the same 64-byte load. Use raw envelope registers.
nes_fds_wave_load(triangle);
__fds_env_set(0x98,0x80,0);
__fds_freq_set(1545);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
sound_end();
}Erwartetes Ergebnis
Ein eigenes 64-stufiges Dreieck spielt bei etwa 440 Hz, pausiert, spielt bei etwa 660 Hz und stoppt. Die zweite Note nutzt nes_fds_wave_load und rohe Hüllkurveneinstellungen. Geprüft werden Audiomodell und Direktstart, nicht BIOS-gesteuerter Diskettenstart.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_fds.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-fds.fds --mapper=fds --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/fds_sound.h:13
Write a 12-bit frequency to 4082/4083; upper control bits in 4083 are cleared.
kitaqfc/kitaqfc/CodeGenerator.cs:5197
case "__fds_freq_set":
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2}, "__fds_freq_set");
// Clamp the direct volume field to six bits and set the fixed-volume control bit.
EmitHelperStart("__fds_freq_set");
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("STA", Mem(0x4082));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("AND", Imm(0x0F)); EmitAsm("STA", Mem(0x4083));
EmitAsm("RTS");__fds_mod_load
Sendet 32 Bytes der Reihe nach an den Modulationstabellen-Schreibport $4088.
Syntax
void __fds_mod_load(const u8* mod32);Parameter
mod32Zweiunddreißig Bytes für die FDS-Modulationstabelle mit je einem Steuercode 0..7. Stoppen Sie die Modulation vor dem Laden.
Rückgabewert
Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.
Bedingungen und Grenzen
Erstellen Sie mit --mapper=fds ein .fds-Abbild. Diese Lektion verwendet Sound ohne Datenträger-I/O. Die Aufnahme nutzt KUROSAKIs direkten RAM-Start und Audiomodell. Die leere Testfirmware implementiert keine BIOS-Routinen und wird nicht verteilt. Normaler Diskettenstart und echte Hardware benötigen eine passende Startumgebung.
Anwendungsbeispiel
// Mapper 20 sound lesson: original 64-step triangle and two sustained pitches.
#include "fc_sound_example.h"
#include "fds_sound.h"
#include "nes_game.h"
__location(0x4087) u8 mod_control;
__location(0x4085) u8 mod_counter;
u8 triangle[64]; u8 modulation[32];
void main(void) {
u8 i;
sound_begin("FDS WAVE AND VOLUME");
for(i=0;i<32;i++) { triangle[i]=(u8)(i*2); triangle[63-i]=(u8)(i*2); modulation[i]=0; }
__fds_sound_enable();
// Halt/reset modulation before writing the 32 values (each writes two slots).
mod_control=0x80; mod_counter=0;
__fds_mod_load(modulation);
__fds_wave_load(triangle);
sound_result[0]=*((u8*)0x4040); sound_result[1]=*((u8*)0x405F);
__fds_freq_set(1031);
__fds_volume_set(24);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
// This macro forwards the same 64-byte load. Use raw envelope registers.
nes_fds_wave_load(triangle);
__fds_env_set(0x98,0x80,0);
__fds_freq_set(1545);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
sound_end();
}Erwartetes Ergebnis
Ein eigenes 64-stufiges Dreieck spielt bei etwa 440 Hz, pausiert, spielt bei etwa 660 Hz und stoppt. Die zweite Note nutzt nes_fds_wave_load und rohe Hüllkurveneinstellungen. Geprüft werden Audiomodell und Direktstart, nicht BIOS-gesteuerter Diskettenstart.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_fds.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-fds.fds --mapper=fds --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/fds_sound.h:11
Stream 32 readable bytes to 4088; the caller prepares modulation state and write conditions.
kitaqfc/kitaqfc/CodeGenerator.cs:5195
case "__fds_mod_load":
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2}, "__fds_mod_load");
EmitHelperStart("__fds_mod_load");
EmitAsm("LDY", Imm(0));
string modLoop = NewGeneratedLabel("fds_mod_loop");
_assembly.Add(Expr.Make(Tag.Label, modLoop));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("STA", Mem(0x4088));
EmitAsm("INY"); EmitAsm("CPY", Imm(32)); EmitAsm("BNE", Rel(modLoop));
EmitAsm("RTS");__fds_sound_enable
Schreibt 0x83 in Disk-/Soundsteuerung $4023 und setzt Wellensteuerung $4089 sowie Geschwindigkeit $408A auf null.
Syntax
void __fds_sound_enable(void);Rückgabewert
Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.
Bedingungen und Grenzen
Erstellen Sie mit --mapper=fds ein .fds-Abbild. Diese Lektion verwendet Sound ohne Datenträger-I/O. Die Aufnahme nutzt KUROSAKIs direkten RAM-Start und Audiomodell. Die leere Testfirmware implementiert keine BIOS-Routinen und wird nicht verteilt. Normaler Diskettenstart und echte Hardware benötigen eine passende Startumgebung.
Anwendungsbeispiel
// Mapper 20 sound lesson: original 64-step triangle and two sustained pitches.
#include "fc_sound_example.h"
#include "fds_sound.h"
#include "nes_game.h"
__location(0x4087) u8 mod_control;
__location(0x4085) u8 mod_counter;
u8 triangle[64]; u8 modulation[32];
void main(void) {
u8 i;
sound_begin("FDS WAVE AND VOLUME");
for(i=0;i<32;i++) { triangle[i]=(u8)(i*2); triangle[63-i]=(u8)(i*2); modulation[i]=0; }
__fds_sound_enable();
// Halt/reset modulation before writing the 32 values (each writes two slots).
mod_control=0x80; mod_counter=0;
__fds_mod_load(modulation);
__fds_wave_load(triangle);
sound_result[0]=*((u8*)0x4040); sound_result[1]=*((u8*)0x405F);
__fds_freq_set(1031);
__fds_volume_set(24);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
// This macro forwards the same 64-byte load. Use raw envelope registers.
nes_fds_wave_load(triangle);
__fds_env_set(0x98,0x80,0);
__fds_freq_set(1545);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
sound_end();
}Erwartetes Ergebnis
Ein eigenes 64-stufiges Dreieck spielt bei etwa 440 Hz, pausiert, spielt bei etwa 660 Hz und stoppt. Die zweite Note nutzt nes_fds_wave_load und rohe Hüllkurveneinstellungen. Geprüft werden Audiomodell und Direktstart, nicht BIOS-gesteuerter Diskettenstart.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_fds.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-fds.fds --mapper=fds --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/fds_sound.h:7
Write 83 to 4023 and zero to 4089/408A to initialize disk/sound register gates.
kitaqfc/kitaqfc/CodeGenerator.cs:5183
case "__fds_sound_enable":
if (!Need(0)) return true;
EmitAsm("LDA", Imm(0x83));
EmitAsm("STA", Mem(0x4023));
EmitAsm("LDA", Imm(0x00));
EmitAsm("STA", Mem(0x4089));
EmitAsm("LDA", Imm(0x00));
EmitAsm("STA", Mem(0x408A));
return true;
// Wave, modulation and frequency operations pass word arguments to their dedicated helpers.__fds_volume_set
Schreibt die unteren sechs Lautstärkebits zusammen mit 0x80 nach $4080 zur direkten Lautstärkesteuerung.
Syntax
void __fds_volume_set(u8 vol);Parameter
volDirekte FDS-Lautstärke 0..63; die Sechsbitmaske lässt 64 auf null umlaufen.
Rückgabewert
Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.
Bedingungen und Grenzen
Erstellen Sie mit --mapper=fds ein .fds-Abbild. Diese Lektion verwendet Sound ohne Datenträger-I/O. Die Aufnahme nutzt KUROSAKIs direkten RAM-Start und Audiomodell. Die leere Testfirmware implementiert keine BIOS-Routinen und wird nicht verteilt. Normaler Diskettenstart und echte Hardware benötigen eine passende Startumgebung.
Anwendungsbeispiel
// Mapper 20 sound lesson: original 64-step triangle and two sustained pitches.
#include "fc_sound_example.h"
#include "fds_sound.h"
#include "nes_game.h"
__location(0x4087) u8 mod_control;
__location(0x4085) u8 mod_counter;
u8 triangle[64]; u8 modulation[32];
void main(void) {
u8 i;
sound_begin("FDS WAVE AND VOLUME");
for(i=0;i<32;i++) { triangle[i]=(u8)(i*2); triangle[63-i]=(u8)(i*2); modulation[i]=0; }
__fds_sound_enable();
// Halt/reset modulation before writing the 32 values (each writes two slots).
mod_control=0x80; mod_counter=0;
__fds_mod_load(modulation);
__fds_wave_load(triangle);
sound_result[0]=*((u8*)0x4040); sound_result[1]=*((u8*)0x405F);
__fds_freq_set(1031);
__fds_volume_set(24);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
// This macro forwards the same 64-byte load. Use raw envelope registers.
nes_fds_wave_load(triangle);
__fds_env_set(0x98,0x80,0);
__fds_freq_set(1545);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
sound_end();
}Erwartetes Ergebnis
Ein eigenes 64-stufiges Dreieck spielt bei etwa 440 Hz, pausiert, spielt bei etwa 660 Hz und stoppt. Die zweite Note nutzt nes_fds_wave_load und rohe Hüllkurveneinstellungen. Geprüft werden Audiomodell und Direktstart, nicht BIOS-gesteuerter Diskettenstart.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_fds.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-fds.fds --mapper=fds --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/fds_sound.h:15
Write (volume & 3F) | 80 to 4080 for direct volume control; masking wraps, rather than clamps.
kitaqfc/kitaqfc/CodeGenerator.cs:5200
case "__fds_volume_set":
if (!Need(1)) return true;
Load1(args[0]);
EmitAsm("AND", Imm(0x3F));
EmitAsm("ORA", Imm(0x80));
EmitAsm("STA", Mem(0x4080));
return true;__fds_wave_load
Aktiviert Wellenschreibzugriffe, kopiert 64 Bytes nach $4040..$407F und setzt $4089 auf null zurück.
Syntax
void __fds_wave_load(const u8* wave64);Parameter
wave64Vierundsechzig FDS-Wellenbytes mit je einer Sechsbitamplitude 0..63. NULL wird nicht geprüft.
Rückgabewert
Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.
Bedingungen und Grenzen
Erstellen Sie mit --mapper=fds ein .fds-Abbild. Diese Lektion verwendet Sound ohne Datenträger-I/O. Die Aufnahme nutzt KUROSAKIs direkten RAM-Start und Audiomodell. Die leere Testfirmware implementiert keine BIOS-Routinen und wird nicht verteilt. Normaler Diskettenstart und echte Hardware benötigen eine passende Startumgebung.
Anwendungsbeispiel
// Mapper 20 sound lesson: original 64-step triangle and two sustained pitches.
#include "fc_sound_example.h"
#include "fds_sound.h"
#include "nes_game.h"
__location(0x4087) u8 mod_control;
__location(0x4085) u8 mod_counter;
u8 triangle[64]; u8 modulation[32];
void main(void) {
u8 i;
sound_begin("FDS WAVE AND VOLUME");
for(i=0;i<32;i++) { triangle[i]=(u8)(i*2); triangle[63-i]=(u8)(i*2); modulation[i]=0; }
__fds_sound_enable();
// Halt/reset modulation before writing the 32 values (each writes two slots).
mod_control=0x80; mod_counter=0;
__fds_mod_load(modulation);
__fds_wave_load(triangle);
sound_result[0]=*((u8*)0x4040); sound_result[1]=*((u8*)0x405F);
__fds_freq_set(1031);
__fds_volume_set(24);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
// This macro forwards the same 64-byte load. Use raw envelope registers.
nes_fds_wave_load(triangle);
__fds_env_set(0x98,0x80,0);
__fds_freq_set(1545);
sound_wait(60);
__fds_volume_set(0); sound_wait(30);
sound_end();
}Erwartetes Ergebnis
Ein eigenes 64-stufiges Dreieck spielt bei etwa 440 Hz, pausiert, spielt bei etwa 660 Hz und stoppt. Die zweite Note nutzt nes_fds_wave_load und rohe Hüllkurveneinstellungen. Geprüft werden Audiomodell und Direktstart, nicht BIOS-gesteuerter Diskettenstart.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_fds.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-fds.fds --mapper=fds --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/fds_sound.h:9
Copy exactly 64 readable bytes to wave RAM with wave writes enabled, then set 4089 to zero.
kitaqfc/kitaqfc/CodeGenerator.cs:5193
case "__fds_wave_load":
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2}, "__fds_wave_load");
EmitHelperStart("__fds_wave_load");
EmitAsm("LDA", Imm(0x80)); EmitAsm("STA", Mem(0x4089));
EmitAsm("LDY", Imm(0));
string waveLoop = NewGeneratedLabel("fds_wave_loop");
_assembly.Add(Expr.Make(Tag.Label, waveLoop));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("STA", new AsmOperand(0x4040, AddressMode.AbsoluteY));
EmitAsm("INY"); EmitAsm("CPY", Imm(64)); EmitAsm("BNE", Rel(waveLoop));
EmitAsm("LDA", Imm(0x00)); EmitAsm("STA", Mem(0x4089));
EmitAsm("RTS");intrinsics.h — Compiler-Intrinsics
__nes_audio_vblank_tick
NMI-Hook, der die BGM- und SFX-Dauern fortschreibt und die APU gemäß der aktuellen Kanalzuordnung aktualisiert.
Syntax
void __nes_audio_vblank_tick(void);Rückgabewert
Kein Rückgabewert.
Bedingungen und Grenzen
Binden Sie audio_vblank.h ein und nehmen Sie audio_vblank.c genau einmal in den Build auf. Der Compiler lässt sowohl den Standard-NMI-Handler als auch einen eigenen NMI-Handler den Musik-Hook einmal aufrufen. Aktivieren Sie PPU-NMI. Die Hauptschleife ruft keine Musikaktualisierung auf.
Dieser Vierkanaltreiber ist auf NTSC abgestimmt: CH1=Impuls 1, CH2=Impuls 2, CH3=Dreieck und CH4=Rauschen, mit den Indizes 0 bis 3. DMC und Erweiterungsaudio werden nicht wiedergegeben. Verwenden Sie ihn nicht gleichzeitig mit direkten APU-Zugriffen, etwa den Effekten in audio.c. Es gibt weder eine PAL-Tonhöhentabelle noch eine PAL-Tempokorrektur.
Rufen Sie diese Funktion nicht direkt aus Anwendungscode auf. Durch das Linken der Musikbibliothek verbindet der Compiler sie mit NMI. Der Hook sichert A/X/Y und verwendet keinen gemeinsamen Zwischenspeicher für Ausdrücke. Hook und Tonhöhentabellen liegen im festen ROM; innerhalb der NMI findet kein ROM-Bankwechsel statt. Die Zustandssicherung gilt nur für den Musik-Hook selbst. Ein eigener NMI-Handler muss weiterhin den Zustand sichern, den seine übrigen Arbeiten verwenden.
Ein Datensatz besteht aus fünf Bytes: delay, CH1, CH2, CH3, CH4. Eine Dauer von 1 bis 255 gibt an, wie viele NMI-Ticks der neue Zustand anhält. Null beendet die Wiedergabe; die übrigen vier Bytes werden ignoriert. Tonwerte von 0 bis 71 entsprechen C2 bis B7 (24=C4). Rauschwerte von 0 bis 15 wählen eine Periode im langen Modus, 16 bis 31 im kurzen Modus. Für jeden Kanal gilt: 255=NES_AUDIO_HOLD behält den Zustand bei, 254=NES_AUDIO_STOP schaltet ihn stumm.
Anwendungsbeispiel
// The linked audio_vblank.c supplies __nes_audio_vblank_tick().
// The compiler calls that hook once per NMI. Do not call it in this loop.
__nmi_wait();Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel spielt eine eigens komponierte vierkanalige Phrase einmal ab. Während das Hauptprogramm 90 Frames lang Ganzzahlberechnungen ohne Nachfüllen ausführt, läuft die Musik weiter. „PHRASE COMPLETE / SILENT“ und „UNDERRUNS 0 / MATH OK“ bedeuten, dass die Wiedergabe beendet ist, kein Datenmangel auftrat und die Rechenergebnisse stimmen. Prüfen Sie in der Audioaufnahme die Impulsmelodie und ihre Begleitung, den Dreiecksbass und das Rauschen. Am Ende herrscht Stille. Das Bild zeigt die Steuerungsergebnisse; den Klang beurteilen Sie anhand der Audioaufnahme.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaqfc\lib\audio_vblank.c .\kitaq-docs\samples\api-examples\fc\audio_vblank.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\audio_vblank.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/audio_vblank.h:70
NMI hook: invoke exactly once per NMI. With a custom handler or a compiler without automatic audio-hook linking, call it there. Never call from main or call it again if the compiler already inserts the hook. Saves A/X/Y, uses no compiler scratch bytes and reads only RAM/fixed ROM.
kitaqfc/lib/audio_vblank.c:150
void __nes_audio_vblank_tick(void){
__asm {
PHA
TXA
PHA
TYA
PHA
LDA nav_paused
BEQ nav_tick_begin
JMP nav_tick_exit
nav_tick_begin:
LDA #0
STA nav_dirty
LDA nav_refresh
BEQ nav_tick_music
LDA #0
STA nav_refresh
LDA #15
STA nav_dirty
// Advance BGM even when an effect temporarily owns its output channel.
nav_tick_music:
LDA nav_active
BNE nav_tick_active
JMP nav_tick_sfx
nav_tick_active:
LDA nav_delay
BEQ nav_tick_next
DEC nav_delay
BEQ nav_tick_next
JMP nav_tick_sfx
nav_tick_next:
LDX nav_tail
CPX nav_head
BNE nav_tick_record
LDA nav_starved
BNE nav_tick_empty_done
INC nav_starved
LDA nav_underruns
CMP #255
BEQ nav_tick_empty_done
INC nav_underruns
nav_tick_empty_done:
JSR nav_silence_music
JMP nav_tick_sfx
nav_tick_record:
LDA #0
STA nav_starved
LDA nav_queue,X
BNE nav_tick_timed
STA nav_active
JSR nav_silence_music
JMP nav_tick_release
nav_tick_timed:
STA nav_delay
LDY #0
// Remember the new BGM notes but do not retrigger channels owned by SFX.
nav_tick_copy:
INX
LDA nav_queue,X
CMP #255
BEQ nav_tick_copy_next
STA nav_notes,Y
LDA nav_bits,Y
AND nav_previous_mask
BNE nav_tick_copy_next
LDA nav_bits,Y
ORA nav_dirty
STA nav_dirty
nav_tick_copy_next:
INY
CPY #4
BNE nav_tick_copy
nav_tick_release:
LDA nav_tail
CLC
ADC #5
CMP #40
BNE nav_tick_publish
LDA #0
nav_tick_publish:
STA nav_tail
// The SFX has its own delay; completion releases its mask automatically.
nav_tick_sfx:
LDA nav_sfx_active
BEQ nav_tick_select
LDA nav_sfx_delay
BEQ nav_sfx_next
DEC nav_sfx_delay
BNE nav_tick_select
nav_sfx_next:
LDX nav_sfx_cursor
CPX nav_sfx_length
BCC nav_sfx_record
LDA #0
STA nav_sfx_active
JMP nav_tick_select
nav_sfx_record:
LDA nav_sfx_queue,X
STA nav_sfx_delay
LDY #0
nav_sfx_copy:
INX
LDA nav_sfx_queue,X
CMP #255
BEQ nav_sfx_copy_next
STA nav_sfx_notes,Y
LDA nav_bits,Y
AND nav_sfx_mask
ORA nav_dirty
STA nav_dirty
nav_sfx_copy_next:
INY
CPY #4
BNE nav_sfx_copy
INX
STX nav_sfx_cursor
nav_tick_select:
LDA #0
STA nav_effective_mask
LDA nav_sfx_active
BEQ nav_tick_restore
LDA nav_sfx_mask
STA nav_effective_mask
// A changed ownership bit forces the newly selected note onto the APU.
nav_tick_restore:
LDA nav_effective_mask
EOR nav_previous_mask
ORA nav_dirty
STA nav_dirty
LDA nav_effective_mask
STA nav_previous_mask
LDA nav_dirty
AND #1
BNE nav_apply_0
JMP nav_done_0
nav_apply_0:
LDA nav_effective_mask
AND #1
BEQ nav_bg_0
LDA nav_sfx_notes+0
JMP nav_value_0
nav_bg_0:
LDA nav_notes+0
nav_value_0:
CMP #254
BNE nav_note_0
LDA nav_enabled
AND #254
STA nav_enabled
STA $4015
JMP nav_done_0
nav_note_0:
TAY
LDA nav_enabled
ORA #1
STA nav_enabled
STA $4015
LDA nav_timbre+0
STA $4000
LDA nav_pulse_lo,Y
STA $4002
LDA nav_pulse_hi,Y
STA $4003
nav_done_0:
LDA nav_dirty
AND #2
BNE nav_apply_1
JMP nav_done_1
nav_apply_1:
LDA nav_effective_mask
AND #2
BEQ nav_bg_1
LDA nav_sfx_notes+1
JMP nav_value_1
nav_bg_1:
LDA nav_notes+1
nav_value_1:
CMP #254
BNE nav_note_1
LDA nav_enabled
AND #253
STA nav_enabled
STA $4015
JMP nav_done_1
nav_note_1:
TAY
LDA nav_enabled
ORA #2
STA nav_enabled
STA $4015
LDA nav_timbre+1
STA $4004
LDA nav_pulse_lo,Y
STA $4006
LDA nav_pulse_hi,Y
STA $4007
nav_done_1:
LDA nav_dirty
AND #4
BNE nav_apply_2
JMP nav_done_2
nav_apply_2:
LDA nav_effective_mask
AND #4
BEQ nav_bg_2
LDA nav_sfx_notes+2
JMP nav_value_2
nav_bg_2:
LDA nav_notes+2
nav_value_2:
CMP #254
BNE nav_note_2
LDA nav_enabled
AND #251
STA nav_enabled
STA $4015
JMP nav_done_2
nav_note_2:
TAY
LDA nav_enabled
ORA #4
STA nav_enabled
STA $4015
LDA nav_timbre+2
STA $4008
LDA nav_triangle_lo,Y
STA $400A
LDA nav_triangle_hi,Y
STA $400B
nav_done_2:
LDA nav_dirty
AND #8
BNE nav_apply_3
JMP nav_done_3
nav_apply_3:
LDA nav_effective_mask
AND #8
BEQ nav_bg_3
LDA nav_sfx_notes+3
JMP nav_value_3
nav_bg_3:
LDA nav_notes+3
nav_value_3:
CMP #254
BNE nav_note_3
LDA nav_enabled
AND #247
STA nav_enabled
STA $4015
JMP nav_done_3
nav_note_3:
TAY
LDA nav_enabled
ORA #8
STA nav_enabled
STA $4015
LDA nav_timbre+3
STA $400C
TYA
AND #16
BEQ nav_noise_long
TYA
AND #15
ORA #128
JMP nav_noise_period
nav_noise_long:
TYA
nav_noise_period:
STA $400E
// A one-shot envelope must outlive its slowest 16-step decay.
// Length index 1 gives 254 half-frame ticks; index 0 cuts it at 10.
LDA nav_timbre+3
AND #16
BNE nav_noise_constant_length
LDA #8
JMP nav_noise_trigger
nav_noise_constant_length:
LDA #0
nav_noise_trigger:
STA $400F
nav_done_3:
nav_tick_exit:
PLA
TAY
PLA
TAX
PLA
RTS
// Internal subroutine: stop BGM channels without disturbing an active effect.
// End/underrun forgets the BGM notes without cutting an independent SFX.
nav_silence_music:
LDA #254
STA nav_notes+0
STA nav_notes+1
STA nav_notes+2
STA nav_notes+3
LDA nav_previous_mask
EOR #15
ORA nav_dirty
STA nav_dirty
RTS
}
}__attr_set
Schreibt das gesamte gepackte Attributbyte an 0x23C0 + (y>>2)*8 + (x>>2) in Nametable 0. X/Y sind Kachelkoordinaten, die einen Attributbereich von 4×4 Kacheln bestimmen. Alle vier 2-Bit-Palettenfelder werden ersetzt; es wird nicht nur ein ausgewählter Quadrant eingemischt.
Syntax
void __attr_set(u8 x, u8 y, u8 attr);Parameter
x / yKachelkoordinaten, keine Pixel. Verwenden Sie
x=0..31undy=0..29für die sichtbare Karte von 32×30 Kacheln; jede Kachel umfasst 8×8 Pixel. Die Byteargumente werden nicht begrenzt und können außerhalb des vorgesehenen Kachelbereichs schreiben.attrGepacktes Attributbyte: Bits 0–1 wählen die oberen linken 2×2 Kacheln, 2–3 die oberen rechten, 4–5 die unteren linken und 6–7 die unteren rechten. Jedes Feld wählt Palette 0–3.
0x55,0xAAund0xFFwählen für alle vier Quadranten jeweils Palette 1, 2 oder 3.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.
Anwendungsbeispiel
for(i=2;i<12;i=(u8)(i+4)) {
__attr_set(i,6,0xFF);__attr_set(i,10,0xFF); // All quadrants select green.
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:134
Write the entire packed attribute byte at 23C0+(y>>2)*8+(x>>2). Use tile coordinates below 32; this does not merge a single palette quadrant.
kitaqfc/kitaqfc/CodeGenerator.cs:4824
case "__attr_set": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1}, funcName);
void EmitHelperAttrSet()
{
EmitHelperStart("__attr_set");
// args: x,y,attr. Attribute address = $23C0 + (y>>2)*8 + (x>>2), nametable 0.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("LSR"); EmitAsm("LSR"); EmitAsm("ASL"); EmitAsm("ASL"); EmitAsm("ASL");
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("LSR"); EmitAsm("LSR"); EmitAsm("CLC"); EmitAsm("ADC", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(0x2002)); EmitAsm("LDA", Imm(0x23)); EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("CLC"); EmitAsm("ADC", Imm(0xC0)); EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("STA", Mem(0x2007));
EmitAsm("RTS");
}__attr_set_nt
Wählt die logische Nametable nt & 3, setzt den PPU-Latch zurück und schreibt ein vollständiges Attributbyte an 0x23C0 + (nt & 3)*0x400 + (y>>2)*8 + (x>>2). Überschreibt alle Palettenquadranten dieses Bytes. Die Tabellenauswahl folgt der aktuellen Cartridge-Spiegelung.
Syntax
void __attr_set_nt(u8 nt, u8 x, u8 y, u8 attr);Parameter
ntAuswahl der logischen Nametable. Nur die unteren zwei Bits zählen (
nt & 3): 4 wählt Tabelle 0, 7 wählt Tabelle 3. Es wird kein unabhängiger Speicher reserviert und keine Mapper-Spiegelung geändert.x / yKachelkoordinaten, keine Pixel. Verwenden Sie
x=0..31undy=0..29für die sichtbare Karte von 32×30 Kacheln; jede Kachel umfasst 8×8 Pixel. Die Byteargumente werden nicht begrenzt und können außerhalb des vorgesehenen Kachelbereichs schreiben.attrGepacktes Attributbyte: Bits 0–1 wählen die oberen linken 2×2 Kacheln, 2–3 die oberen rechten, 4–5 die unteren linken und 6–7 die unteren rechten. Jedes Feld wählt Palette 0–3.
0x55,0xAAund0xFFwählen für alle vier Quadranten jeweils Palette 1, 2 oder 3.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.
Anwendungsbeispiel
for(i=18;i<28;i=(u8)(i+4)) {
__attr_set_nt(4,i,6,0x55);__attr_set_nt(4,i,10,0x55); // nt&3=0; red.
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:136
Write a whole packed attribute byte in nametable nt&3; other quadrant fields are overwritten.
kitaqfc/kitaqfc/CodeGenerator.cs:4825
case "__attr_set_nt": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1,1}, funcName);
// Palette loaders receive a word pointer; their fixed data layout is handled by the runtime helper.
void EmitHelperAttrSetNt()
{
EmitHelperStart("__attr_set_nt");
// args: nt,x,y,attr. Attribute address = $23C0 + nt*$400 + (y>>2)*8 + (x>>2).
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("AND", Imm(3));
EmitAsm("ASL");
EmitAsm("ASL");
EmitAsm("CLC");
EmitAsm("ADC", Imm(0x23));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("LSR"); EmitAsm("LSR"); EmitAsm("ASL"); EmitAsm("ASL"); EmitAsm("ASL");
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("LSR"); EmitAsm("LSR"); EmitAsm("CLC"); EmitAsm("ADC", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(0x2002)); EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("CLC"); EmitAsm("ADC", Imm(0xC0)); EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(CallArgBase + 3)); EmitAsm("STA", Mem(0x2007));
EmitAsm("RTS");
}__bankof
Ermittelt die logische ROM-Banknummer eines namentlich angegebenen Datenobjekts oder einer Funktion. Verwendet deren Platzierung; es wird weder die aktive Bank abgefragt noch ein Zeiger dereferenziert.
Syntax
u8 bank = __bankof(symbol);Parameter
symbolName eines ROM-Datenobjekts oder einer Funktion mit bekannter Compiler- oder Linkerplatzierung. Eine Zeigervariable bezeichnet hier nicht ihr Zielobjekt; Laufzeitausdrücke sind keine zulässigen Symbolnamen.
Rückgabewert
Eine u8-Banknummer. Null kann gemeinsames ROM kennzeichnen, ist aber nicht auf jedem Ziel ein gültiger Umschaltwert.
Anwendungsbeispiel
bank_expect(__bankof(payload)==3,3);
bank_expect(__bankof(callback)==3,4);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel prüft, dass payload und callback beide in Bank 3 liegen. Damit werden Platzierungsmetadaten geprüft; getrennte Lese- und Callback-Prüfungen bestätigen das Bankverhalten zur Laufzeit.
Das vollständige Programm zeigt Datenzugriffe über Bank und Nahadresse, bankübergreifende Callbacks und den Unterschied zwischen Bibliothekszustand und tatsächlicher Zuordnung. Alle Zahlen sind hexadezimal. BANK IN und BANK OUT müssen 02 zeigen, BYTE HEX D3, WORD HEX 5C7A sowie FAILED CHECKS und FIRST FAILURE jeweils 00. CGB und FC zeigen blaue Schrift, DMG schwarze.
CALL COUNT zeigt 02: Das Makro far_call und __farcall rufen den Callback jeweils einmal auf.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\banked_data_calls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:77
PRG bank Resolve a symbol or function placement to its byte bank number at compilation time.
kitaqfc/kitaqfc/CodeGenerator.cs:6906
if (expr.MatchAny(Tag.Call, out funcExpr, out args) &&
funcExpr != null &&
funcExpr.Match(Tag.Name, out name) &&
string.Equals(name, "__bankof", StringComparison.Ordinal) &&
args != null &&
args.Length == 1 &&
args[0] != null &&
args[0].Match(Tag.Name, out string bankedSymbol))
{
// Resolve __bankof from placement metadata and keep the byte-sized bank value.
value = ResolveSymbolBank(bankedSymbol) & 0xFF;
return true;
}__bankswitch
Wählt über die Mapper-spezifische Routine des Compilers eine logische PRG-Bank und aktualisiert deren internen Zustand. Die Zuordnung bleibt bestehen; der separate Zustand der bank-Bibliothek bleibt unverändert.
Syntax
void __bankswitch(u8 bank);Parameter
bankVorzeichenlose 8-Bit-ROM-Banknummer. Bei Aufrufen muss sie zur Funktionsplatzierung passen, bei Lesezugriffen wählt sie die Quelle. Verwenden Sie den gültigen Bereich des Modultyps.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Ein dauerhafter Bankwechsel darf weder den laufenden Code noch seine Rückkehradresse ausblenden. Legen Sie den Aufruf, gegebenenfalls bank_switch selbst und den anschließend ausgeführten Code wie im Beispiel in die gemeinsame beziehungsweise feste Bank 0.
Der Bibliothekszustand beginnt bei null und erkennt die Startzuordnung des Moduls nicht. Initialisieren Sie ihn mit bank_switch. Direkte Intrinsics und Mapper-Schreibzugriffe aktualisieren diesen separaten Zustand nicht; bank_get_current kann die tatsächliche Hardwarezuordnung daher nicht prüfen.
FC-Banknummern sind logische Nummern des Compilers. Null bezeichnet gemeinsames ROM, kein Umschaltziel; die Hilfsroutine setzt null auf Bank 1 um. Bei SUROM512 sind nur 1–30 gültig: Eine direkt an das Umschalt-Intrinsic übergebene Konstante außerhalb des Bereichs führt zu einem Buildfehler; ein ungültiger Laufzeitwert wählt 1 und setzt den Mapper-Fehlerzustand. Lesen Sie gemeinsames ROM direkt, statt Bank null anzufordern.
Anwendungsbeispiel
__bankswitch(1);
bank_expect(home1[0]==0xA1,19);
bank_expect(bank_get_current()==2,20); // Intrinsics bypass the library record.
__bankswitch(2);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Der anfängliche Aufruf bank_switch(2) speichert und wählt Bank 2. Ein späterer direkter Wechsel wählt Bank 1 und liest A1 aus home1, während bank_get_current weiterhin 2 meldet. Nach dem Zurückschalten liefert home2 wieder B2.
Das vollständige Programm zeigt Datenzugriffe über Bank und Nahadresse, bankübergreifende Callbacks und den Unterschied zwischen Bibliothekszustand und tatsächlicher Zuordnung. Alle Zahlen sind hexadezimal. BANK IN und BANK OUT müssen 02 zeigen, BYTE HEX D3, WORD HEX 5C7A sowie FAILED CHECKS und FIRST FAILURE jeweils 00. CGB und FC zeigen blaue Schrift, DMG schwarze.
CALL COUNT zeigt 02: Das Makro far_call und __farcall rufen den Callback jeweils einmal auf.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\banked_data_calls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:79
Select the mapper-specific PRG bank; the common bank and legal bank range depend on the target board.
kitaqfc/kitaqfc/CodeGenerator.cs:4746
case "__prg_bank_set":
case "__bankswitch":
if (!Need(1)) return true;
if (Program.NesMapperProfile.IsSurom512 && TryEvaluateConstant(args[0], out int constantBank) && (constantBank < 1 || constantBank > 30))
{
Program.Error("error KQFC2603 KQFC-SUROM-BANK-RANGE: {0} constant bank {1} is outside 1..30; common bank 0 is not a switch target.", funcName, constantBank);
return true;
}
_mapperWritingFunctions.Add(_currentFunctionName);
Load1(args[0]); EmitAsm("JSR", Abs("__kq_prg_set_bank_a")); return true;__bit_clear
Löscht das gewählte Flag auf 0, ohne den Rest des Bytes zu löschen. Entfernen Sie so eine gespeicherte Bedingung, etwa einen vorübergehenden Status. Erneutes Löschen desselben Bits lässt es bei 0.
Syntax
void __bit_clear(u8* base, u16 bit);Parameter
baseZeiger auf das erste Byte des Flagarrays in CPU-zugänglichem Speicher. Der Aufruf reserviert und initialisiert das Array nicht. Verwenden Sie beschreibbaren RAM zum Setzen, Löschen und Umschalten; die Prüfung liest nur.
bitBei null beginnender Bitindex als
u16(0–65535), kein Byte-Offset und keine Bitmaske. Index 0 wählt Maske0x01in Byte 0, Index 7 wählt0x80in Byte 0 und Index 8 wählt0x01in Byte 1. Allgemein ist der Byte-Offsetindex / 8und die Maske1 << (index % 8). Für ein N-Byte-Array müssenindex < 8*Nund ein gültiger CPU-Adressbereich gelten.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Der aufrufende Code muss genügend Speicher bereitstellen. Es gibt weder Längenargument noch Bereichsbegrenzung, Bankwechsel oder automatische Grafikübertragung. Eine große Bitnummer kann ein Byte weit außerhalb des reservierten Arrays auswählen. Halten Sie die Speicherzuordnung stabil; verwenden Sie diese RAM-Flagfunktionen nicht als Abkürzung für Hardwareregister mit Nebenwirkungen beim Lesen oder Schreiben.
Setzen, Löschen und Umschalten lesen ein Byte, ändern das gewählte Bit und schreiben das Byte zurück. Diese Folge ist nicht atomar. Koordinieren Sie Schreibzugriffe von Hauptschleife und Interrupt-Handlern, damit eine zwischenzeitliche Änderung an einem anderen Bit desselben Bytes nicht verloren geht.
FC-Code kann einen konstanten Index direkt einbetten oder eine erzeugte Hilfsfunktion aufrufen. Beide Formen verwenden dieselbe Bitnummerierung und Ergebnismaske. Die Hilfsfunktionen teilen sich Argument- und Arbeitsspeicher; treten Sie während eines anderen Hilfsaufrufs nicht aus NMI/IRQ erneut ein.
Anwendungsbeispiel
__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.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das vollständige Programm erreicht diesen Schritt mit Byte 0 gleich 0xA6. Löschen von Bit 2 ergibt 0xA2 (binär 10100010, dezimal 162); CLEAR BIT2 muss 162 zeigen. Erneutes Löschen von Bit 2 ändert nichts. Die anderen gespeicherten Flags bleiben bei beiden Aufrufen erhalten.
Erwartete FC-Zeilen in Dezimaldarstellung: TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE entsteht durch Setzen von Index 9 im zweiten Byte: 0x80 wird 0x82, Byte 0 bleibt unverändert. Das aufgenommene FC-Bild zeigt blaue Schrift auf Schwarz.
Das Programm prüft jeden angezeigten Zustandswechsel und zeigt die Fehlerzahl. Setzen, Löschen und Umschalten liefern selbst keinen Wert; die Zeilen zeigen das resultierende RAM-Byte. Separate Emulatorprüfungen vergleichen alle 520 Bytes geschützter Puffer in 136 Fällen je Plattform: konstante und laufzeitabhängige Indizes über Byte- und 256-Byte-Grenzen sowie alle acht möglichen Bitprüfmasken ungleich null. Dies sind RAM-Zustandsprüfungen, keine Zeichendemonstration. Physische Hardware und gleichzeitige Interrupt-Schreibzugriffe wurden nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\bit_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:45
Clear the indexed bit in caller storage; no bounds check or interrupt exclusion is inserted.
kitaqfc/kitaqfc/CodeGenerator.cs:4640
case "__bit_clear":
if (TryEmitBitIntrinsicInline(funcName, args, ctx)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2}, "__bit_clear");
bool TryEmitBitIntrinsicInline(string funcName, Expr[] args, FunctionContext ctx)
{
args = args ?? Array.Empty<Expr>();
if (args.Length != 2) return false;
if (!TryEvaluateConstant(args[1], out int bit) || bit < 0) return false;
// Split the bit number into a byte displacement and an in-byte mask.
int byteOffset = bit >> 3;
if (byteOffset > 255) return false;
int mask = 1 << (bit & 7);
int[] ptr = AcquireTemps(2);
try
{
EmitLoadValue(args[0], ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitAsm("LDY", Imm(byteOffset));
EmitAsm("LDA", IndY(ptr[0]));
switch (funcName)
{
case "__bit_test":
EmitAsm("AND", Imm(mask));
break;
case "__bit_set":
EmitAsm("ORA", Imm(mask));
EmitAsm("STA", IndY(ptr[0]));
break;
case "__bit_clear":
EmitAsm("AND", Imm((~mask) & 0xFF));
EmitAsm("STA", IndY(ptr[0]));
break;
case "__bit_toggle":
EmitAsm("EOR", Imm(mask));
EmitAsm("STA", IndY(ptr[0]));
break;
default:
return false;
}
}
finally { ReleaseTemps(ptr); }
return true;
}
void EmitBitHelper(string name, string op)
{
EmitHelperStart(name);
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(7));
EmitAsm("TAX");
string maskDone = NewGeneratedLabel("bit_mask_done");
string maskLoop = NewGeneratedLabel("bit_mask_loop");
EmitAsm("BEQ", Rel(maskDone));
_assembly.Add(Expr.Make(Tag.Label, maskLoop));
EmitAsm("ASL", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("DEX");
EmitAsm("BNE", Rel(maskLoop));
_assembly.Add(Expr.Make(Tag.Label, maskDone));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
for (int i = 0; i < 3; i++) { EmitAsm("LSR", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("ROR", Mem(_runtimeIntrinsicTmp1Address)); }
// Fold the high displacement into the pointer; indirect-Y adds the low byte and its carry.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDY", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", IndY(CallArgBase));
switch (op)
{
case "test":
EmitAsm("AND", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
break;
case "set":
EmitAsm("ORA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
case "clear":
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("AND", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
case "toggle":
EmitAsm("EOR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
}
}__bit_set
Setzt das gewählte Flag auf 1 und erhält die übrigen sieben Bits desselben Bytes. Markieren Sie damit etwa einen erledigten Auftrag oder einen eingesammelten Gegenstand. Ein bereits gesetztes Bit bleibt unverändert; dies ist kein Umschalten.
Syntax
void __bit_set(u8* base, u16 bit);Parameter
baseZeiger auf das erste Byte des Flagarrays in CPU-zugänglichem Speicher. Der Aufruf reserviert und initialisiert das Array nicht. Verwenden Sie beschreibbaren RAM zum Setzen, Löschen und Umschalten; die Prüfung liest nur.
bitBei null beginnender Bitindex als
u16(0–65535), kein Byte-Offset und keine Bitmaske. Index 0 wählt Maske0x01in Byte 0, Index 7 wählt0x80in Byte 0 und Index 8 wählt0x01in Byte 1. Allgemein ist der Byte-Offsetindex / 8und die Maske1 << (index % 8). Für ein N-Byte-Array müssenindex < 8*Nund ein gültiger CPU-Adressbereich gelten.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Der aufrufende Code muss genügend Speicher bereitstellen. Es gibt weder Längenargument noch Bereichsbegrenzung, Bankwechsel oder automatische Grafikübertragung. Eine große Bitnummer kann ein Byte weit außerhalb des reservierten Arrays auswählen. Halten Sie die Speicherzuordnung stabil; verwenden Sie diese RAM-Flagfunktionen nicht als Abkürzung für Hardwareregister mit Nebenwirkungen beim Lesen oder Schreiben.
Setzen, Löschen und Umschalten lesen ein Byte, ändern das gewählte Bit und schreiben das Byte zurück. Diese Folge ist nicht atomar. Koordinieren Sie Schreibzugriffe von Hauptschleife und Interrupt-Handlern, damit eine zwischenzeitliche Änderung an einem anderen Bit desselben Bytes nicht verloren geht.
FC-Code kann einen konstanten Index direkt einbetten oder eine erzeugte Hilfsfunktion aufrufen. Beide Formen verwenden dieselbe Bitnummerierung und Ergebnismaske. Die Hilfsfunktionen teilen sich Argument- und Arbeitsspeicher; treten Sie während eines anderen Hilfsaufrufs nicht aus NMI/IRQ erneut ein.
Anwendungsbeispiel
__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.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Setzen Sie Bit 1 von 0xA4: 10100100 | 00000010 ergibt 10100110, also 0xA6 (dezimal 166). SET BIT1 muss 166 zeigen. Das Beispiel setzt das Bit erneut und prüft, dass das Byte 0xA6 bleibt; wiederholtes Setzen invertiert das Flag also nicht.
Erwartete FC-Zeilen in Dezimaldarstellung: TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE entsteht durch Setzen von Index 9 im zweiten Byte: 0x80 wird 0x82, Byte 0 bleibt unverändert. Das aufgenommene FC-Bild zeigt blaue Schrift auf Schwarz.
Das Programm prüft jeden angezeigten Zustandswechsel und zeigt die Fehlerzahl. Setzen, Löschen und Umschalten liefern selbst keinen Wert; die Zeilen zeigen das resultierende RAM-Byte. Separate Emulatorprüfungen vergleichen alle 520 Bytes geschützter Puffer in 136 Fällen je Plattform: konstante und laufzeitabhängige Indizes über Byte- und 256-Byte-Grenzen sowie alle acht möglichen Bitprüfmasken ungleich null. Dies sind RAM-Zustandsprüfungen, keine Zeichendemonstration. Physische Hardware und gleichzeitige Interrupt-Schreibzugriffe wurden nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\bit_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:43
Set the indexed bit in caller storage without changing the other seven bits in that byte.
kitaqfc/kitaqfc/CodeGenerator.cs:4637
case "__bit_set":
if (TryEmitBitIntrinsicInline(funcName, args, ctx)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2}, "__bit_set");
bool TryEmitBitIntrinsicInline(string funcName, Expr[] args, FunctionContext ctx)
{
args = args ?? Array.Empty<Expr>();
if (args.Length != 2) return false;
if (!TryEvaluateConstant(args[1], out int bit) || bit < 0) return false;
// Split the bit number into a byte displacement and an in-byte mask.
int byteOffset = bit >> 3;
if (byteOffset > 255) return false;
int mask = 1 << (bit & 7);
int[] ptr = AcquireTemps(2);
try
{
EmitLoadValue(args[0], ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitAsm("LDY", Imm(byteOffset));
EmitAsm("LDA", IndY(ptr[0]));
switch (funcName)
{
case "__bit_test":
EmitAsm("AND", Imm(mask));
break;
case "__bit_set":
EmitAsm("ORA", Imm(mask));
EmitAsm("STA", IndY(ptr[0]));
break;
case "__bit_clear":
EmitAsm("AND", Imm((~mask) & 0xFF));
EmitAsm("STA", IndY(ptr[0]));
break;
case "__bit_toggle":
EmitAsm("EOR", Imm(mask));
EmitAsm("STA", IndY(ptr[0]));
break;
default:
return false;
}
}
finally { ReleaseTemps(ptr); }
return true;
}
void EmitBitHelper(string name, string op)
{
EmitHelperStart(name);
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(7));
EmitAsm("TAX");
string maskDone = NewGeneratedLabel("bit_mask_done");
string maskLoop = NewGeneratedLabel("bit_mask_loop");
EmitAsm("BEQ", Rel(maskDone));
_assembly.Add(Expr.Make(Tag.Label, maskLoop));
EmitAsm("ASL", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("DEX");
EmitAsm("BNE", Rel(maskLoop));
_assembly.Add(Expr.Make(Tag.Label, maskDone));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
for (int i = 0; i < 3; i++) { EmitAsm("LSR", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("ROR", Mem(_runtimeIntrinsicTmp1Address)); }
// Fold the high displacement into the pointer; indirect-Y adds the low byte and its carry.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDY", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", IndY(CallArgBase));
switch (op)
{
case "test":
EmitAsm("AND", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
break;
case "set":
EmitAsm("ORA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
case "clear":
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("AND", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
case "toggle":
EmitAsm("EOR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
}
}__bit_test
Liest ein Flag aus einem gepackten Bitarray, ohne das Array zu ändern. So teilen sich acht unabhängige Ein/Aus-Flags ein RAM-Byte. Das Ergebnis zeigt, ob das gewählte Bit gelöscht oder gesetzt ist; seine numerische Darstellung hängt vom Zielsystem ab.
Syntax
u8 __bit_test(u8* base, u16 bit);Parameter
baseZeiger auf das erste Byte des Flagarrays in CPU-zugänglichem Speicher. Der Aufruf reserviert und initialisiert das Array nicht. Verwenden Sie beschreibbaren RAM zum Setzen, Löschen und Umschalten; die Prüfung liest nur.
bitBei null beginnender Bitindex als
u16(0–65535), kein Byte-Offset und keine Bitmaske. Index 0 wählt Maske0x01in Byte 0, Index 7 wählt0x80in Byte 0 und Index 8 wählt0x01in Byte 1. Allgemein ist der Byte-Offsetindex / 8und die Maske1 << (index % 8). Für ein N-Byte-Array müssenindex < 8*Nund ein gültiger CPU-Adressbereich gelten.
Rückgabewert
u8: 0 bei gelöschtem Bit, sonst seine Maske: 1, 2, 4, 8, 16, 32, 64 oder 128. Das Ergebnis ist base[bit >> 3] & (1 << (bit & 7)). Ein gesetztes Bit 7 liefert beispielsweise 128. Prüfen Sie mit result != 0 auf ein gesetztes Flag; ein Vergleich nur mit 1 übersieht Flags an anderen Bitpositionen.
Bedingungen und Grenzen
Der aufrufende Code muss genügend Speicher bereitstellen. Es gibt weder Längenargument noch Bereichsbegrenzung, Bankwechsel oder automatische Grafikübertragung. Eine große Bitnummer kann ein Byte weit außerhalb des reservierten Arrays auswählen. Halten Sie die Speicherzuordnung stabil; verwenden Sie diese RAM-Flagfunktionen nicht als Abkürzung für Hardwareregister mit Nebenwirkungen beim Lesen oder Schreiben.
FC-Code kann einen konstanten Index direkt einbetten oder eine erzeugte Hilfsfunktion aufrufen. Beide Formen verwenden dieselbe Bitnummerierung und Ergebnismaske. Die Hilfsfunktionen teilen sich Argument- und Arbeitsspeicher; treten Sie während eines anderen Hilfsaufrufs nicht aus NMI/IRQ erneut ein.
Anwendungsbeispiel
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.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Beginnen Sie mit Byte 0 gleich 0xA4 (binär 10100100). Die Prüfung von Bit 1 liefert 0, die von Bit 7 auf GB 1 und auf FC 128. Das Beispiel wandelt den Wert ungleich null außerdem in ein gemeinsames Ein/Aus-Flag um und prüft das unveränderte Originalbyte. Beachten Sie TEST OFF und TEST ON im Bild unten.
Erwartete FC-Zeilen in Dezimaldarstellung: TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE entsteht durch Setzen von Index 9 im zweiten Byte: 0x80 wird 0x82, Byte 0 bleibt unverändert. Das aufgenommene FC-Bild zeigt blaue Schrift auf Schwarz.
Das Programm prüft jeden angezeigten Zustandswechsel und zeigt die Fehlerzahl. Setzen, Löschen und Umschalten liefern selbst keinen Wert; die Zeilen zeigen das resultierende RAM-Byte. Separate Emulatorprüfungen vergleichen alle 520 Bytes geschützter Puffer in 136 Fällen je Plattform: konstante und laufzeitabhängige Indizes über Byte- und 256-Byte-Grenzen sowie alle acht möglichen Bitprüfmasken ungleich null. Dies sind RAM-Zustandsprüfungen, keine Zeichendemonstration. Physische Hardware und gleichzeitige Interrupt-Schreibzugriffe wurden nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\bit_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:41
Return base[bit>>3] & (1<<(bit&7)): zero or the bit mask, not a normalized Boolean.
kitaqfc/kitaqfc/CodeGenerator.cs:4634
case "__bit_test":
if (TryEmitBitIntrinsicInline(funcName, args, ctx)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2}, "__bit_test");
bool TryEmitBitIntrinsicInline(string funcName, Expr[] args, FunctionContext ctx)
{
args = args ?? Array.Empty<Expr>();
if (args.Length != 2) return false;
if (!TryEvaluateConstant(args[1], out int bit) || bit < 0) return false;
// Split the bit number into a byte displacement and an in-byte mask.
int byteOffset = bit >> 3;
if (byteOffset > 255) return false;
int mask = 1 << (bit & 7);
int[] ptr = AcquireTemps(2);
try
{
EmitLoadValue(args[0], ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitAsm("LDY", Imm(byteOffset));
EmitAsm("LDA", IndY(ptr[0]));
switch (funcName)
{
case "__bit_test":
EmitAsm("AND", Imm(mask));
break;
case "__bit_set":
EmitAsm("ORA", Imm(mask));
EmitAsm("STA", IndY(ptr[0]));
break;
case "__bit_clear":
EmitAsm("AND", Imm((~mask) & 0xFF));
EmitAsm("STA", IndY(ptr[0]));
break;
case "__bit_toggle":
EmitAsm("EOR", Imm(mask));
EmitAsm("STA", IndY(ptr[0]));
break;
default:
return false;
}
}
finally { ReleaseTemps(ptr); }
return true;
}
void EmitBitHelper(string name, string op)
{
EmitHelperStart(name);
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(7));
EmitAsm("TAX");
string maskDone = NewGeneratedLabel("bit_mask_done");
string maskLoop = NewGeneratedLabel("bit_mask_loop");
EmitAsm("BEQ", Rel(maskDone));
_assembly.Add(Expr.Make(Tag.Label, maskLoop));
EmitAsm("ASL", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("DEX");
EmitAsm("BNE", Rel(maskLoop));
_assembly.Add(Expr.Make(Tag.Label, maskDone));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
for (int i = 0; i < 3; i++) { EmitAsm("LSR", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("ROR", Mem(_runtimeIntrinsicTmp1Address)); }
// Fold the high displacement into the pointer; indirect-Y adds the low byte and its carry.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDY", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", IndY(CallArgBase));
switch (op)
{
case "test":
EmitAsm("AND", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
break;
case "set":
EmitAsm("ORA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
case "clear":
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("AND", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
case "toggle":
EmitAsm("EOR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
}
}__bit_toggle
Invertiert das gewählte Flag: 0 wird 1 und 1 wird 0. Die übrigen Bits bleiben erhalten. Zwei Aufrufe auf dasselbe Bit stellen den ursprünglichen Zustand wieder her, sofern dazwischen nichts anderes dieses Bit schreibt.
Syntax
void __bit_toggle(u8* base, u16 bit);Parameter
baseZeiger auf das erste Byte des Flagarrays in CPU-zugänglichem Speicher. Der Aufruf reserviert und initialisiert das Array nicht. Verwenden Sie beschreibbaren RAM zum Setzen, Löschen und Umschalten; die Prüfung liest nur.
bitBei null beginnender Bitindex als
u16(0–65535), kein Byte-Offset und keine Bitmaske. Index 0 wählt Maske0x01in Byte 0, Index 7 wählt0x80in Byte 0 und Index 8 wählt0x01in Byte 1. Allgemein ist der Byte-Offsetindex / 8und die Maske1 << (index % 8). Für ein N-Byte-Array müssenindex < 8*Nund ein gültiger CPU-Adressbereich gelten.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Der aufrufende Code muss genügend Speicher bereitstellen. Es gibt weder Längenargument noch Bereichsbegrenzung, Bankwechsel oder automatische Grafikübertragung. Eine große Bitnummer kann ein Byte weit außerhalb des reservierten Arrays auswählen. Halten Sie die Speicherzuordnung stabil; verwenden Sie diese RAM-Flagfunktionen nicht als Abkürzung für Hardwareregister mit Nebenwirkungen beim Lesen oder Schreiben.
Setzen, Löschen und Umschalten lesen ein Byte, ändern das gewählte Bit und schreiben das Byte zurück. Diese Folge ist nicht atomar. Koordinieren Sie Schreibzugriffe von Hauptschleife und Interrupt-Handlern, damit eine zwischenzeitliche Änderung an einem anderen Bit desselben Bytes nicht verloren geht.
FC-Code kann einen konstanten Index direkt einbetten oder eine erzeugte Hilfsfunktion aufrufen. Beide Formen verwenden dieselbe Bitnummerierung und Ergebnismaske. Die Hilfsfunktionen teilen sich Argument- und Arbeitsspeicher; treten Sie während eines anderen Hilfsaufrufs nicht aus NMI/IRQ erneut ein.
Anwendungsbeispiel
__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.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das vollständige Programm beginnt diesen Schritt mit 0xA2. Umschalten von Bit 7 ergibt 0x22 (dezimal 34), also zeigt TOGGLE BIT7 034. Erneutes Umschalten stellt 0xA2 wieder her; RESTORE zeigt 162. Dies belegt beide Richtungen der Invertierung bei Erhalt der übrigen Bits.
Erwartete FC-Zeilen in Dezimaldarstellung: TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE entsteht durch Setzen von Index 9 im zweiten Byte: 0x80 wird 0x82, Byte 0 bleibt unverändert. Das aufgenommene FC-Bild zeigt blaue Schrift auf Schwarz.
Das Programm prüft jeden angezeigten Zustandswechsel und zeigt die Fehlerzahl. Setzen, Löschen und Umschalten liefern selbst keinen Wert; die Zeilen zeigen das resultierende RAM-Byte. Separate Emulatorprüfungen vergleichen alle 520 Bytes geschützter Puffer in 136 Fällen je Plattform: konstante und laufzeitabhängige Indizes über Byte- und 256-Byte-Grenzen sowie alle acht möglichen Bitprüfmasken ungleich null. Dies sind RAM-Zustandsprüfungen, keine Zeichendemonstration. Physische Hardware und gleichzeitige Interrupt-Schreibzugriffe wurden nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\bit_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\bit_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:47
XOR the indexed bit in caller storage; coordinate concurrent writers to avoid lost updates.
kitaqfc/kitaqfc/CodeGenerator.cs:4643
case "__bit_toggle":
if (TryEmitBitIntrinsicInline(funcName, args, ctx)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2}, "__bit_toggle");
// Arithmetic variants share argument packing here; signed/fixed-point arithmetic is implemented by their named helpers.
bool TryEmitBitIntrinsicInline(string funcName, Expr[] args, FunctionContext ctx)
{
args = args ?? Array.Empty<Expr>();
if (args.Length != 2) return false;
if (!TryEvaluateConstant(args[1], out int bit) || bit < 0) return false;
// Split the bit number into a byte displacement and an in-byte mask.
int byteOffset = bit >> 3;
if (byteOffset > 255) return false;
int mask = 1 << (bit & 7);
int[] ptr = AcquireTemps(2);
try
{
EmitLoadValue(args[0], ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitAsm("LDY", Imm(byteOffset));
EmitAsm("LDA", IndY(ptr[0]));
switch (funcName)
{
case "__bit_test":
EmitAsm("AND", Imm(mask));
break;
case "__bit_set":
EmitAsm("ORA", Imm(mask));
EmitAsm("STA", IndY(ptr[0]));
break;
case "__bit_clear":
EmitAsm("AND", Imm((~mask) & 0xFF));
EmitAsm("STA", IndY(ptr[0]));
break;
case "__bit_toggle":
EmitAsm("EOR", Imm(mask));
EmitAsm("STA", IndY(ptr[0]));
break;
default:
return false;
}
}
finally { ReleaseTemps(ptr); }
return true;
}
void EmitBitHelper(string name, string op)
{
EmitHelperStart(name);
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(7));
EmitAsm("TAX");
string maskDone = NewGeneratedLabel("bit_mask_done");
string maskLoop = NewGeneratedLabel("bit_mask_loop");
EmitAsm("BEQ", Rel(maskDone));
_assembly.Add(Expr.Make(Tag.Label, maskLoop));
EmitAsm("ASL", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("DEX");
EmitAsm("BNE", Rel(maskLoop));
_assembly.Add(Expr.Make(Tag.Label, maskDone));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
for (int i = 0; i < 3; i++) { EmitAsm("LSR", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("ROR", Mem(_runtimeIntrinsicTmp1Address)); }
// Fold the high displacement into the pointer; indirect-Y adds the low byte and its carry.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDY", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", IndY(CallArgBase));
switch (op)
{
case "test":
EmitAsm("AND", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
break;
case "set":
EmitAsm("ORA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
case "clear":
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("AND", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
case "toggle":
EmitAsm("EOR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("RTS");
break;
}
}__chr_bank_set
Wählt eine CHR-Bank. Bei MMC3 wird R0 gewählt und das 2-KiB-Fenster bei PPU $0000–$07FF geändert. Bei CNROM wird der gesamte 8-KiB-Bereich $0000–$1FFF geändert.
Syntax
void __chr_bank_set(u8 bank);Parameter
bankBei CNROM ist dies eine 8-KiB-CHR-Banknummer. Bei MMC3 wählt eine 1-KiB-Banknummer ein 2-KiB-Fenster: Das niederwertigste Bit wird ignoriert, sodass 2 und 3 beide die 1-KiB-Banken 2 und 3 auswählen. Verfügbare Banken hängen von der CHR-ROM-Kapazität ab; die API prüft keine Grenzen.
Rückgabewert
Kein Rückgabewert.
Bedingungen und Grenzen
Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.
Das Schreiben von 0 oder 1 ins MMC3-Bankauswahlregister löscht auch PRG-Modusbit 6 und CHR-Inversionsbit 7. Kombinieren Sie dies nicht mit einem PRG/CHR-Layout, das diese Bits gesetzt benötigt. Umschalten während der Darstellung verändert nachfolgende Musterzugriffe. Das Beispiel prüft bei ausgeschalteter Darstellung und stellt vor der Ergebnisanzeige die Schriftbanken wieder her.
Diese drei Intrinsics unterstützen CNROM und MMC3. Andere Platinen werden beim Kompilieren abgewiesen. SUROM verwendet die CHR-Steuerung auch zur Auswahl des äußeren PRG-Bereichs und erlaubt diese CHR-Vorgänge nicht.
Anwendungsbeispiel
__chr_bank_set(2);
mapper_result[1] = mapper_read(0x03FF);
mapper_result[2] = mapper_read(0x07FF);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasmAnwendungsbeispiel
__chr_bank_set(2); chr_result[0]=chr_marker();
__chr_bank_set0(1); chr_result[1]=chr_marker();
__chr_bank_set1(3); chr_result[2]=chr_marker();Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Die CNROM-Lektion zeigt nach SET 2, SET0 1 und SET1 3 die Marker 33, 25 und 41. Sie liest eigens erstellte Markerbytes über die PPU und zeigt damit, dass alle drei Namen den gesamten 8-KiB-Musterbereich umschalten.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_chr_cnrom.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_chr_cnrom.nes --mapper=cnrom --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:316
Select a CNROM bank or MMC3 register 0 bank; unsupported mapper profiles produce a compile error.
__chr_bank_set0
Wählt MMC3 R0 und ändert das 2-KiB-Fenster bei PPU $0000–$07FF. Entspricht __chr_bank_set. Bei CNROM wird der gesamte 8-KiB-Bereich geändert.
Syntax
void __chr_bank_set0(u8 bank);Parameter
bankBei CNROM ist dies eine 8-KiB-CHR-Banknummer. Bei MMC3 wählt eine 1-KiB-Banknummer ein 2-KiB-Fenster: Das niederwertigste Bit wird ignoriert, sodass 2 und 3 beide die 1-KiB-Banken 2 und 3 auswählen. Verfügbare Banken hängen von der CHR-ROM-Kapazität ab; die API prüft keine Grenzen.
Rückgabewert
Kein Rückgabewert.
Bedingungen und Grenzen
Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.
Das Schreiben von 0 oder 1 ins MMC3-Bankauswahlregister löscht auch PRG-Modusbit 6 und CHR-Inversionsbit 7. Kombinieren Sie dies nicht mit einem PRG/CHR-Layout, das diese Bits gesetzt benötigt. Umschalten während der Darstellung verändert nachfolgende Musterzugriffe. Das Beispiel prüft bei ausgeschalteter Darstellung und stellt vor der Ergebnisanzeige die Schriftbanken wieder her.
Diese drei Intrinsics unterstützen CNROM und MMC3. Andere Platinen werden beim Kompilieren abgewiesen. SUROM verwendet die CHR-Steuerung auch zur Auswahl des äußeren PRG-Bereichs und erlaubt diese CHR-Vorgänge nicht.
Anwendungsbeispiel
__chr_bank_set0(4);
mapper_result[3] = mapper_read(0x03FF);
mapper_result[4] = mapper_read(0x07FF);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasmAnwendungsbeispiel
__chr_bank_set(2); chr_result[0]=chr_marker();
__chr_bank_set0(1); chr_result[1]=chr_marker();
__chr_bank_set1(3); chr_result[2]=chr_marker();Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Die CNROM-Lektion zeigt nach SET 2, SET0 1 und SET1 3 die Marker 33, 25 und 41. Sie liest eigens erstellte Markerbytes über die PPU und zeigt damit, dass alle drei Namen den gesamten 8-KiB-Musterbereich umschalten.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_chr_cnrom.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_chr_cnrom.nes --mapper=cnrom --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:318
Use the same CNROM/MMC3 register 0 selection as __chr_bank_set.
__chr_bank_set1
Wählt MMC3 R1 und ändert das 2-KiB-Fenster bei PPU $0800–$0FFF, während R0 unverändert bleibt. CNROM besitzt kein unabhängiges zweites Fenster; dort wird der gesamte 8-KiB-Bereich geändert.
Syntax
void __chr_bank_set1(u8 bank);Parameter
bankBei CNROM ist dies eine 8-KiB-CHR-Banknummer. Bei MMC3 wählt eine 1-KiB-Banknummer ein 2-KiB-Fenster: Das niederwertigste Bit wird ignoriert, sodass 2 und 3 beide die 1-KiB-Banken 2 und 3 auswählen. Verfügbare Banken hängen von der CHR-ROM-Kapazität ab; die API prüft keine Grenzen.
Rückgabewert
Kein Rückgabewert.
Bedingungen und Grenzen
Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.
Das Schreiben von 0 oder 1 ins MMC3-Bankauswahlregister löscht auch PRG-Modusbit 6 und CHR-Inversionsbit 7. Kombinieren Sie dies nicht mit einem PRG/CHR-Layout, das diese Bits gesetzt benötigt. Umschalten während der Darstellung verändert nachfolgende Musterzugriffe. Das Beispiel prüft bei ausgeschalteter Darstellung und stellt vor der Ergebnisanzeige die Schriftbanken wieder her.
Diese drei Intrinsics unterstützen CNROM und MMC3. Andere Platinen werden beim Kompilieren abgewiesen. SUROM verwendet die CHR-Steuerung auch zur Auswahl des äußeren PRG-Bereichs und erlaubt diese CHR-Vorgänge nicht.
Anwendungsbeispiel
__chr_bank_set1(6);
mapper_result[5] = mapper_read(0x0BFF);
mapper_result[6] = mapper_read(0x0FFF);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasmAnwendungsbeispiel
__chr_bank_set(2); chr_result[0]=chr_marker();
__chr_bank_set0(1); chr_result[1]=chr_marker();
__chr_bank_set1(3); chr_result[2]=chr_marker();Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Die CNROM-Lektion zeigt nach SET 2, SET0 1 und SET1 3 die Marker 33, 25 und 41. Sie liest eigens erstellte Markerbytes über die PPU und zeigt damit, dass alle drei Namen den gesamten 8-KiB-Musterbereich umschalten.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_chr_cnrom.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_chr_cnrom.nes --mapper=cnrom --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:320
Select MMC3 register 1, or the same full CNROM bank register; this is mapper-specific banking.
__copy16
Kopiert genau 16 Bytes von src nach dst, ohne Längenargument. Damit lässt sich etwa ein 16-Byte-Datensatz oder eine GB/NES-Kachel im CPU-zugänglichen Speicher kopieren. 16 bezeichnet Bytes, nicht Bits; Grafikdaten werden dadurch nicht an die Anzeige übertragen.
Syntax
void __copy16(u8* dst, const u8* src);Parameter
dstZeiger auf das erste beschreibbare Zielbyte. Stellen Sie genügend zusammenhängenden Speicher für die gesamte Operation bereit; ein Puffer wird nicht automatisch angelegt.
srcZeiger auf das erste lesbare Quellbyte. Der gesamte Bereich muss während der Kopie zugänglich und unverändert bleiben.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Verwenden Sie Quell- und Zielbereiche, die sich nicht überlappen. Diese Routinen kopieren vorwärts und bieten nicht das überlappungssichere Verhalten von memmove. Schreibzugriffe auf noch ungelesene Quellbytes können die spätere Kopie verfälschen.
Die Operation wird vor der Rückkehr abgeschlossen. Sie schaltet keine ROM/RAM-Bänke um und begrenzt den Bereich nicht automatisch auf Puffergrenzen. Halten Sie die Speicherabbildung stabil, verwenden Sie gültige Bereiche und stimmen Sie Zugriffe mit Interrupt-Routinen ab, die dieselben Puffer verwenden.
Auf dem FC ist dst eine CPU-Adresse, keine PPU-Adresse. Eine Kopie ins CPU-RAM aktualisiert weder CHR noch Nametables oder Paletten; verwenden Sie dafür die PPU/VRAM-APIs. Generierte Hilfsroutinen teilen sich Argument- und Arbeitsspeicher. Rufen Sie sie während einer laufenden Operation nicht erneut aus einer NMI/IRQ-Routine auf.
Anwendungsbeispiel
memory_reset();
__copy16(memory_destination+1,memory_source+1);
memory_copy_16=memory_check(16,0); // Exactly 16 bytes, not 16 bits.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel kopiert mit __copy16 ab Offset 1 ein bekanntes Muster von 16 Bytes. memory_check vergleicht die vollständigen Quell- und Zielarrays; die Zeile COPY16 muss 016 anzeigen. Das erste Byte bleibt als Schutzbereich frei. Auch die Bytes nach dem Nutzdatenbereich müssen unverändert bleiben.
Das vollständige Programm initialisiert vor jeder Operation zwei 264-Byte-Puffer und prüft danach jedes Quell- und Zielbyte sowie die unveränderten Schutzbereiche. Erwartet werden COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 und FAILED 000. WORD/BYTE bezeichnen die Zählerbreite, nicht die Größe der kopierten Elemente. Die Zahlen stammen aus den Prüfungen des Beispiels; diese void-Funktionen liefern keine Rückgabewerte.
Die Aufnahmen zeigen schwarze Schrift auf Weiß beim DMG, blaue Schrift auf Weiß beim CGB und blaue Schrift auf Schwarz beim FC. Das Beispiel prüft RAM-Operationen; die Schrift stellt die Ergebnisse dar und demonstriert keine Zeichenprimitive. Weitere Emulatorprüfungen decken je Plattform 88 Speicherfälle ab: konstante und zur Laufzeit bestimmte Längen von 0, 1, 16, 17, 32, 33, 64, 65 und 255 Bytes sowie 256 und 257 bei 16-Bit-Zählern. Auf echter Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\memory_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:27
Copy exactly 16 bytes forward; both ranges must be valid and suitably non-overlapping.
kitaqfc/kitaqfc/CodeGenerator.cs:4620
case "__copy16":
if (!Need(2)) return true;
EmitFixedMemcpy(args[0], args[1], 16, ctx); return true;
void EmitFixedMemcpy(Expr dstExpr, Expr srcExpr, int len, FunctionContext ctx)
{
int[] ptr = AcquireTemps(4);
try
{
EmitLoadValue(dstExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitLoadValue(srcExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[2]));
EmitAsm("STX", Mem(ptr[3]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", IndY(ptr[2]));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
}
// kitaqfc/kitaqfc/Lowerer.cs:2128
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
Kopiert genau 32 Bytes von src nach dst, ohne Längenargument. Das eignet sich für einen festen 32-Byte-Datensatz oder zwei benachbarte 16-Byte-Kachelmuster im CPU-zugänglichen Speicher. Die Kopie erfolgt byteweise vorwärts, weder als einzelner 32-Bit-Lesezugriff noch mit Bankumschaltung.
Syntax
void __copy32(u8* dst, const u8* src);Parameter
dstZeiger auf das erste beschreibbare Zielbyte. Stellen Sie genügend zusammenhängenden Speicher für die gesamte Operation bereit; ein Puffer wird nicht automatisch angelegt.
srcZeiger auf das erste lesbare Quellbyte. Der gesamte Bereich muss während der Kopie zugänglich und unverändert bleiben.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Verwenden Sie Quell- und Zielbereiche, die sich nicht überlappen. Diese Routinen kopieren vorwärts und bieten nicht das überlappungssichere Verhalten von memmove. Schreibzugriffe auf noch ungelesene Quellbytes können die spätere Kopie verfälschen.
Die Operation wird vor der Rückkehr abgeschlossen. Sie schaltet keine ROM/RAM-Bänke um und begrenzt den Bereich nicht automatisch auf Puffergrenzen. Halten Sie die Speicherabbildung stabil, verwenden Sie gültige Bereiche und stimmen Sie Zugriffe mit Interrupt-Routinen ab, die dieselben Puffer verwenden.
Auf dem FC ist dst eine CPU-Adresse, keine PPU-Adresse. Eine Kopie ins CPU-RAM aktualisiert weder CHR noch Nametables oder Paletten; verwenden Sie dafür die PPU/VRAM-APIs. Generierte Hilfsroutinen teilen sich Argument- und Arbeitsspeicher. Rufen Sie sie während einer laufenden Operation nicht erneut aus einer NMI/IRQ-Routine auf.
Anwendungsbeispiel
memory_reset();
__copy32(memory_destination+1,memory_source+1);
memory_copy_32=memory_check(32,0);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel kopiert mit __copy32 ab Offset 1 ein bekanntes Muster von 32 Bytes. memory_check vergleicht die vollständigen Quell- und Zielarrays; die Zeile COPY32 muss 032 anzeigen. Das erste Byte bleibt als Schutzbereich frei. Auch die Bytes nach dem Nutzdatenbereich müssen unverändert bleiben.
Das vollständige Programm initialisiert vor jeder Operation zwei 264-Byte-Puffer und prüft danach jedes Quell- und Zielbyte sowie die unveränderten Schutzbereiche. Erwartet werden COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 und FAILED 000. WORD/BYTE bezeichnen die Zählerbreite, nicht die Größe der kopierten Elemente. Die Zahlen stammen aus den Prüfungen des Beispiels; diese void-Funktionen liefern keine Rückgabewerte.
Die Aufnahmen zeigen schwarze Schrift auf Weiß beim DMG, blaue Schrift auf Weiß beim CGB und blaue Schrift auf Schwarz beim FC. Das Beispiel prüft RAM-Operationen; die Schrift stellt die Ergebnisse dar und demonstriert keine Zeichenprimitive. Weitere Emulatorprüfungen decken je Plattform 88 Speicherfälle ab: konstante und zur Laufzeit bestimmte Längen von 0, 1, 16, 17, 32, 33, 64, 65 und 255 Bytes sowie 256 und 257 bei 16-Bit-Zählern. Auf echter Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\memory_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:29
Copy exactly 32 bytes forward; bank selection and source lifetime belong to the caller.
kitaqfc/kitaqfc/CodeGenerator.cs:4623
case "__copy32":
if (!Need(2)) return true;
EmitFixedMemcpy(args[0], args[1], 32, ctx); return true;
// Geometry helpers receive byte coordinates; the size array describes ABI packing, not result width.
void EmitFixedMemcpy(Expr dstExpr, Expr srcExpr, int len, FunctionContext ctx)
{
int[] ptr = AcquireTemps(4);
try
{
EmitLoadValue(dstExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitLoadValue(srcExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[2]));
EmitAsm("STX", Mem(ptr[3]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", IndY(ptr[2]));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
}
// kitaqfc/kitaqfc/Lowerer.cs:2128
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);
}__dot2_q8_8
Berechnet ein vorzeichenloses Skalarprodukt mit zwei Termen.
Syntax
u16 __dot2_q8_8(u16 a0, u16 a1, u8 b0, u8 b1);Parameter
a0Vorzeichenloses Wort, 0..65535.
a1Vorzeichenloses Wort, 0..65535.
b0Vorzeichenloser Koeffizient, 0..255.
b1Vorzeichenloser Koeffizient, 0..255.
Rückgabewert
Gibt die unteren 16 Bits von a0*b0+a1*b1 zurück.
Bedingungen und Grenzen
Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.
Anwendungsbeispiel
// Compare literal coefficients with values read from RAM.
result[22]=__dot2_q8_8(513,769,64,127);
v0=513;v1=769;v2=64;v3=127;
result[23]=__dot2_q8_8(v0,v1,v2,v3);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.
Der Ausschnitt speichert folgende 16-Bit-Werte: result[22]=64959, result[23]=64959. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chrImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:63
Return a0*b0+a1*b1 modulo 65536. The FC backend inserts no Q8.8 rescaling or rounding.
kitaqfc/kitaqfc/CodeGenerator.cs:4654
case "__dot2_q8_8": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,1,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__dot3_q8_8
Berechnet ein vorzeichenloses Skalarprodukt mit drei Termen.
Syntax
u16 __dot3_q8_8(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);Parameter
a0Vorzeichenloses Wort, 0..65535.
a1Vorzeichenloses Wort, 0..65535.
a2Vorzeichenloses Wort, 0..65535.
b0Vorzeichenloser Koeffizient, 0..255.
b1Vorzeichenloser Koeffizient, 0..255.
b2Vorzeichenloser Koeffizient, 0..255.
Rückgabewert
Gibt die unteren 16 Bits von a0*b0+a1*b1+a2*b2 zurück.
Bedingungen und Grenzen
Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.
Anwendungsbeispiel
// Compare literal coefficients with values read from RAM.
result[24]=__dot3_q8_8(513,769,1025,64,127,3);
v0=513;v1=769;v2=1025;v3=64;v4=127;v5=3;
result[25]=__dot3_q8_8(v0,v1,v2,v3,v4,v5);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.
Der Ausschnitt speichert folgende 16-Bit-Werte: result[24]=2498, result[25]=2498. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chrImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:65
Sum three unsigned word-by-byte products modulo 65536 without a final scaling shift.
kitaqfc/kitaqfc/CodeGenerator.cs:4655
case "__dot3_q8_8": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,2,1,1,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__exp_pad_read1
__exp_pad_read1 ruft dieselbe Compilerroutine wie __pad_read1_d1 auf. D1 an $4016 wird mit identischer Zustandsübernahme, identischem Achtbit-Ergebnis und denselben Nebenwirkungen gelesen. Der Name bezeichnet den Erweiterungseingang; er aktiviert keinen anderen Gerätemodus und kombiniert D0 nicht mit D1.
Syntax
u8 __exp_pad_read1(void);Rückgabewert
Eine 8-Bit-Haltemaske in roher NES-Reihenfolge. Jedes gesetzte Bit steht für eine gedrückte Taste, 0 für keine. Kein auf 0/1 normalisierter Wahrheitswert.
Bedingungen und Grenzen
Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.
Verwenden Sie ein Peripheriegerät mit einem kompatiblen seriellen Achtbit-Controllerstrom auf der gewählten D1-Leitung. Die Funktion erkennt das Gerät nicht und implementiert nicht jedes Erweiterungsprotokoll. Jeder Aufruf übernimmt beide Controllerströme neu; beenden Sie einen vollständigen Lesevorgang, bevor Sie den nächsten beginnen. Die Ergebnisse sind neue Momentaufnahmen und aktualisieren weder input_* noch nes_pad1_*.
Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.
Anwendungsbeispiel
expansion_first=__exp_pad_read1(); // Same helper as __pad_read1_d1.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Dieses Programm vergleicht normale D0-Eingaben mit D1 und dem digitalen Mikrofonpegel. Halten Sie A auf Controller 1 und B auf Controller 2 bis zur Erfassung gedrückt. NORMAL P1 und NORMAL P2 zeigen 001 und 002. Im beigefügten KUROSAKI-Lauf zeigen D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL und MIC DIRECT jeweils 000, weil diese Leitungen nicht emuliert werden. Die erfassten Werte bleiben anschließend stehen. Mit geeigneten Peripheriegeräten und implementierten Signalleitungen hängen die Werte von den tatsächlichen Eingaben ab; deuten Sie sie anhand der obigen Bitbelegung, statt diese Nullen zu erwarten.
Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\expansion_input.c -I .\kitaq-docs\samples -I .\kitaqfc\lib -o .\out\expansion_input.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:227
Alias the port-1 D1 controller reader; the attached device must use the expected serial protocol.
__exp_pad_read2
__exp_pad_read2 ruft dieselbe Compilerroutine wie __pad_read2_d1 auf. D1 an $4017 wird mit identischer Zustandsübernahme, identischem Achtbit-Ergebnis und denselben Nebenwirkungen gelesen. Der Name bezeichnet den Erweiterungseingang; er aktiviert keinen anderen Gerätemodus und kombiniert D0 nicht mit D1.
Syntax
u8 __exp_pad_read2(void);Rückgabewert
Eine 8-Bit-Haltemaske in roher NES-Reihenfolge. Jedes gesetzte Bit steht für eine gedrückte Taste, 0 für keine. Kein auf 0/1 normalisierter Wahrheitswert.
Bedingungen und Grenzen
Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.
Verwenden Sie ein Peripheriegerät mit einem kompatiblen seriellen Achtbit-Controllerstrom auf der gewählten D1-Leitung. Die Funktion erkennt das Gerät nicht und implementiert nicht jedes Erweiterungsprotokoll. Jeder Aufruf übernimmt beide Controllerströme neu; beenden Sie einen vollständigen Lesevorgang, bevor Sie den nächsten beginnen. Die Ergebnisse sind neue Momentaufnahmen und aktualisieren weder input_* noch nes_pad1_*.
Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.
Anwendungsbeispiel
expansion_second=__exp_pad_read2(); // Same helper as __pad_read2_d1.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Dieses Programm vergleicht normale D0-Eingaben mit D1 und dem digitalen Mikrofonpegel. Halten Sie A auf Controller 1 und B auf Controller 2 bis zur Erfassung gedrückt. NORMAL P1 und NORMAL P2 zeigen 001 und 002. Im beigefügten KUROSAKI-Lauf zeigen D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL und MIC DIRECT jeweils 000, weil diese Leitungen nicht emuliert werden. Die erfassten Werte bleiben anschließend stehen. Mit geeigneten Peripheriegeräten und implementierten Signalleitungen hängen die Werte von den tatsächlichen Eingaben ab; deuten Sie sie anhand der obigen Bitbelegung, statt diese Nullen zu erwarten.
Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\expansion_input.c -I .\kitaq-docs\samples -I .\kitaqfc\lib -o .\out\expansion_input.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:229
Alias the port-2 D1 controller reader; this is not an expansion-device discovery operation.
__far_memcpy
Wählt vorübergehend eine ROM-Bank, kopiert len Byte mit aufsteigenden Adressen von src nach dst und stellt anschließend die vom Compiler gespeicherte Bank wieder her. Auf dieser Plattform verwenden __farmemcpy und __far_memcpy dieselbe Implementierung.
Syntax
void __far_memcpy(u8* dst, u8 bank, u16 src, u16 len);Parameter
dstZiel in beschreibbarem Speicher mit genügend Platz für alle kopierten Byte. Abgesehen von einem Transfer mit Länge null erfolgt keine Prüfung auf Nullzeiger oder ungültige Zieladressen.
bank8-Bit-Bankauswahl für die Quell-ROM-Zuordnung. Die Bank muss zur Cartridge und zur Bankaufteilung des Compilers passen. Sie wird getrennt von der Quelladresse angegeben.
src16-Bit-CPU-Adresse der Quelldaten in der gewählten ROM-Zuordnung. Die FC-Deklaration erwartet
u16; wandeln Sie einen Ressourcenzeiger mit(u16)pointerum.lenByteanzahl, 0–65535. Bei null wird kein Speicher kopiert; Bankauswahl und Wiederherstellung werden trotzdem ausgeführt.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Der gesamte Quellbereich muss in der vorgesehenen ROM-Zuordnung lesbar und der gesamte Zielbereich beschreibbar sein. Die Kopie schaltet am Fensterende nicht automatisch zur nächsten Bank, behandelt Überlappungen nicht wie memmove und steuert keine sicheren VRAM-Schreibzeitpunkte. Verwenden Sie für VRAM die Grafikübertragungs-APIs.
Die Beispiele legen den aufrufenden Code in die feste Bank 0, damit ein Wechsel der Quellbank den laufenden Code nicht ausblendet. Die Übertragungen sind synchron. Unterbrechen Sie sie nicht durch eine weitere bankwechselnde Kopie. Die Hilfsfunktion allein macht nicht die gesamte Anwendung interruptsicher.
Anwendungsbeispiel
__far_memcpy(buffer,payload_bank,(u16)payload_pointer,32);
expect(*asset_bank_marker==0xA1 && bank_get_current()==1,27);
for(i=0;i<32;i++) expect(buffer[(__safe_index u8)i]==expected_shape_byte(i),28);
buffer[32]=0x55;
__far_memcpy(buffer+32,2,(u16)asset_shapes,0);
expect(buffer[32]==0x55 && *asset_bank_marker==0xA1,30);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Kopiert zwei Muster mit insgesamt 32 Byte aus Bank 2, ausgehend von Bank 1. Das Programm vergleicht jedes Byte, prüft die Wiederherstellung von Bank 1 und den unveränderten Prüfwert 0x55 bei einer Kopie der Länge null.
Erwartetes Bild: Eine rote Reihe beginnt bei (16,32) und belegt 72×8 Pixel. Dreimal folgen ein 8×8-Dreieck, ein 8×8-Quadrat mit einem Pixel Rand und eine Kachel mit vier gefüllten linken Spalten. Das Dreieck wächst von einem Pixel oben links auf acht Pixel in der untersten Zeile.
Auf dem FC zeigt das blaue Paar bei (16,64) __far_memcpy, das grüne bei (16,96) asset_load_raw mit anschließender CHR-Übertragung. Jedes Paar belegt 16×8 Pixel auf schwarzem Hintergrund. So werden die kopierten Bilddaten sichtbar, nicht nur numerische Statuswerte.
Erwartet werden FAILED CHECKS 000 und FIRST FAILURE 00. Die Prüfung führt 300 Bilder aus und vergleicht Text, Formmasken und Farben mit den Sollwerten. Dies ist ein Emulatornachweis, kein Test auf physischer Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\asset_banks.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\asset_banks.nes --mapper=mmc1 --board=surom512 --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:86
Switch to the source bank, copy len bytes forward and restore the previous bank. Keep destination RAM and helper code accessible throughout; overlapping copies are not memmove.
__farcall
Ruft eine benannte Funktion ohne Argumente auf. Der FC-Compiler wählt anhand ihrer Bank einen direkten Aufruf oder eine Hilfsroutine mit Bankwiederherstellung. Das Argument bank wird ausgewertet; die tatsächliche Auswahl folgt jedoch der Platzierung der Funktion.
Syntax
__farcall(bank, function_name);Parameter
bankVorzeichenlose 8-Bit-ROM-Banknummer. Bei Aufrufen muss sie zur Funktionsplatzierung passen, bei Lesezugriffen wählt sie die Quelle. Verwenden Sie den gültigen Bereich des Modultyps.
funcDeklarierter Name einer Funktion ohne Parameter. Dieses Intrinsic für benannte Aufrufe akzeptiert keine Variable mit einer Funktionsadresse. Das Argument bank wird nicht als Callback-Parameter weitergereicht.
Rückgabewert
Der Ergebnistyp richtet sich nach der aufgerufenen Funktion. Ein void-Callback liefert keinen Wert; u8- und u16-Callbacks liefern ihren Skalarwert. Das gezeigte Beispiel verwendet void-Callbacks.
Bedingungen und Grenzen
Diese Aufrufe reichen keine Benutzerargumente weiter und machen den Callback nicht interrupt-reentrant. Verwalten Sie seine Daten, seinen Stack und seine Interruptnutzung korrekt. Die Bankwiederherstellung ist mit verschachtelten und wiederholten Aufrufen geprüft; reale Hardware und beliebige Interruptverschachtelungen deckt dieses Beispiel nicht ab.
FC-Banknummern sind logische Nummern des Compilers. Null bezeichnet gemeinsames ROM, kein Umschaltziel; die Hilfsroutine setzt null auf Bank 1 um. Bei SUROM512 sind nur 1–30 gültig: Eine direkt an das Umschalt-Intrinsic übergebene Konstante außerhalb des Bereichs führt zu einem Buildfehler; ein ungültiger Laufzeitwert wählt 1 und setzt den Mapper-Fehlerzustand. Lesen Sie gemeinsames ROM direkt, statt Bank null anzufordern.
Anwendungsbeispiel
__farcall(3,callback);
bank_expect(callback_count==2 && callback_byte==0xD3,16);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
An dieser Stelle des vollständigen Programms erhöht __farcall den Wert von callback_count auf 2. Der Callback liest D3 aus Bank 3. Ein späterer Nahzugriff auf home2 mit Ergebnis B2 bestätigt die Rückkehr bei eingeblendeter Bank 2.
Das vollständige Programm zeigt Datenzugriffe über Bank und Nahadresse, bankübergreifende Callbacks und den Unterschied zwischen Bibliothekszustand und tatsächlicher Zuordnung. Alle Zahlen sind hexadezimal. BANK IN und BANK OUT müssen 02 zeigen, BYTE HEX D3, WORD HEX 5C7A sowie FAILED CHECKS und FIRST FAILURE jeweils 00. CGB und FC zeigen blaue Schrift, DMG schwarze.
CALL COUNT zeigt 02: Das Makro far_call und __farcall rufen den Callback jeweils einmal auf.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\banked_data_calls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:83
Call a declared zero-argument function; placement selects its bank and its declared type determines the result.
kitaqfc/kitaqfc/CodeGenerator.cs:4131
if (string.Equals(funcName, "__farcall", StringComparison.Ordinal))
{
Expr[] farArgs = args ?? Array.Empty<Expr>();
if (farArgs.Length != 2)
{
Program.Error("error KQ0000: __farcall(bank, func) expects exactly 2 arguments.");
return;
}
string targetName;
if (!farArgs[1].Match(Tag.Name, out targetName))
{
Program.Error("error KQ0000: __farcall(bank, func) requires the 2nd argument to be a function name.");
return;
}
// This intrinsic supplies no callback arguments. A variable name
// is not a static function target, even when its value is a pointer.
if (!_functionParameters.TryGetValue(targetName, out FieldInfo[] farParameters))
{
Program.Error("error KQFC2610: __farcall requires a declared function name; use an ordinary typed call for a function pointer.");
return;
}
if (farParameters.Length != 0)
{
Program.Error("error KQFC2610: __farcall requires a callback with no parameters.");
return;
}
if (Program.NesMapperProfile.IsSurom512 &&
TryEvaluateConstant(farArgs[0], out int constantFarBank) &&
(constantFarBank < 1 || constantFarBank > 30))
{
Program.Error("error KQFC2603 KQFC-SUROM-BANK-RANGE: __farcall constant bank {0} is outside 1..30; common bank 0 is not a switch target.", constantFarBank);
return;
}
// Evaluate the requested far-call bank byte; the following dispatch uses the target function bank metadata.
EmitLoadValue(farArgs[0], ctx, 1);
int callerBank = ctx == null ? 0 : ctx.Bank;
int calleeBank = GetFunctionBank(targetName);
if (ShouldUseFdsOverlayThunk(callerBank, calleeBank))
{
string fdsThunkName = EnsureFdsOverlayThunk(targetName, calleeBank);
EmitAsm("JSR", Abs(fdsThunkName));
RecordCallEdge(targetName, calleeBank, "fds_overlay_thunk", viaThunk: true, viaFarcall: true);
return;
}
bool bankedPrg = Program.NesMapperProfile.SupportsPrgBanking || Program.NesMapperProfile.UsesDuplicatedCommonBank;
bool directSafe = !bankedPrg || calleeBank == 0 || calleeBank == callerBank;
if (directSafe)
{
EmitAsm("JSR", Abs(targetName));
RecordCallEdge(targetName, calleeBank, "direct", viaThunk: false, viaFarcall: true);
return;
}
string thunkName = EnsureBankThunk(targetName, calleeBank);
EmitAsm("JSR", Abs(thunkName));
RecordCallEdge(targetName, calleeBank, "bank_thunk", viaThunk: true, viaFarcall: true);
return;
}__farmemcpy
__farmemcpy ist eine alternative Schreibweise des FC-Intrinsics __far_memcpy. Argumente sowie Kopier- und Bankwiederherstellungsroutine sind identisch. Die Schreibweise ändert weder Übertragungsverfahren noch Geschwindigkeit. Das Beispiel liest damit Kachelmuster aus dem ROM ins RAM.
Wählt vorübergehend eine ROM-Bank, kopiert len Byte mit aufsteigenden Adressen von src nach dst und stellt anschließend die vom Compiler gespeicherte Bank wieder her. Auf dieser Plattform verwenden __farmemcpy und __far_memcpy dieselbe Implementierung.
Syntax
void __farmemcpy(u8* dst, u8 bank, u16 src, u16 len);Parameter
dstZiel in beschreibbarem Speicher mit genügend Platz für alle kopierten Byte. Abgesehen von einem Transfer mit Länge null erfolgt keine Prüfung auf Nullzeiger oder ungültige Zieladressen.
bank8-Bit-Bankauswahl für die Quell-ROM-Zuordnung. Die Bank muss zur Cartridge und zur Bankaufteilung des Compilers passen. Sie wird getrennt von der Quelladresse angegeben.
src16-Bit-CPU-Adresse der Quelldaten in der gewählten ROM-Zuordnung. Die FC-Deklaration erwartet
u16; wandeln Sie einen Ressourcenzeiger mit(u16)pointerum.lenByteanzahl, 0–65535. Bei null wird kein Speicher kopiert; Bankauswahl und Wiederherstellung werden trotzdem ausgeführt.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Der Compiler registriert diese Schreibweise direkt; eine zusätzliche Deklaration in intrinsics.h ist nicht erforderlich. Die Argumente sind der Reihe nach ein RAM-Zeiger, eine 8-Bit-Banknummer, eine 16-Bit-CPU-Adresse und eine 16-Bit-Byteanzahl. src ist kein Offset vom Anfang der ROM-Datei.
Der gesamte Quellbereich muss in der vorgesehenen ROM-Zuordnung lesbar und der gesamte Zielbereich beschreibbar sein. Die Kopie schaltet am Fensterende nicht automatisch zur nächsten Bank, behandelt Überlappungen nicht wie memmove und steuert keine sicheren VRAM-Schreibzeitpunkte. Verwenden Sie für VRAM die Grafikübertragungs-APIs.
Die Beispiele legen den aufrufenden Code in die feste Bank 0, damit ein Wechsel der Quellbank den laufenden Code nicht ausblendet. Die Übertragungen sind synchron. Unterbrechen Sie sie nicht durch eine weitere bankwechselnde Kopie. Die Hilfsfunktion allein macht nicht die gesamte Anwendung interruptsicher.
Bei len gleich null bleiben alle Zielbytes unverändert. Bankauswahl und Wiederherstellung finden dennoch statt. Die Funktion hat keinen Rückgabewert.
Anwendungsbeispiel
__farmemcpy(buffer,payload_bank,(u16)payload_pointer,32);
expect(*asset_bank_marker==0xA1 && bank_get_current()==1,27);
for(i=0;i<32;i++) expect(buffer[(__safe_index u8)i]==expected_shape_byte(i),28);
buffer[32]=0x55;
__farmemcpy(buffer+32,2,(u16)asset_shapes,0);
expect(buffer[32]==0x55 && *asset_bank_marker==0xA1,30);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Kopiert zwei Muster mit insgesamt 32 Byte aus Bank 2, ausgehend von Bank 1. Das Programm vergleicht jedes Byte, prüft die Wiederherstellung von Bank 1 und den unveränderten Prüfwert 0x55 bei einer Kopie der Länge null.
Erwartetes Bild: Eine rote Reihe beginnt bei (16,32) und belegt 72×8 Pixel. Dreimal folgen ein 8×8-Dreieck, ein 8×8-Quadrat mit einem Pixel Rand und eine Kachel mit vier gefüllten linken Spalten. Das Dreieck wächst von einem Pixel oben links auf acht Pixel in der untersten Zeile.
Das blaue Dreieck und der blaue Quadratumriss bei Pixel (16,64) stellen die von __farmemcpy kopierten Bytes dar. Das grüne Paar darunter bei (16,96) stammt von asset_load_raw. Jedes Paar belegt 16×8 Pixel auf schwarzem Hintergrund. Die kopierten Bytes werden zur Anzeige ins CHR-RAM übertragen.
Erwartet werden FAILED CHECKS 000 und FIRST FAILURE 00. Die Prüfung führt 300 Bilder aus und vergleicht Text, Formmasken und Farben mit den Sollwerten. Dies ist ein Emulatornachweis, kein Test auf physischer Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\far_copy_alias.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\far_copy_alias.nes --mapper=mmc1 --board=surom512 --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/kitaqfc/CodeGenerator.cs:264
kitaqfc/kitaqfc/CodeGenerator.cs:4668
case "__far_memcpy":
case "__farmemcpy":
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,1,2,2}, "__far_memcpy");__farpeek16
Liest bei kurzzeitig gewählter Bank einen 16-Bit-Wert in Little-Endian-Reihenfolge an addr und addr+1 und stellt die Bank wieder her. Dieses Intrinsic wird von far_data_read16 verwendet.
Syntax
u16 __farpeek16(u8 bank, u16 addr);Parameter
bankVorzeichenlose 8-Bit-ROM-Banknummer. Bei Aufrufen muss sie zur Funktionsplatzierung passen, bei Lesezugriffen wählt sie die Quelle. Verwenden Sie den gültigen Bereich des Modultyps.
addr16-Bit-CPU-Adresse der Quelldaten in der gewählten ROM-Zuordnung. Die FC-Deklaration erwartet
u16; wandeln Sie einen Ressourcenzeiger mit(u16)pointerum.
Rückgabewert
Das gelesene Little-Endian-Wort als u16 (0–65535).
Bedingungen und Grenzen
Das gesamte Byte oder Wort muss in der gewählten CPU-Zuordnung lesbar sein. Zeiger werden nicht geprüft, Adressen nicht begrenzt und Fenstergrenzen lösen keinen automatischen Wechsel zur nächsten ROM-Bank aus. Ein Wort benötigt zwei gültige aufeinanderfolgende Bytes.
Fernlesezugriffe und Kopien arbeiten synchron und nutzen gemeinsamen Arbeitsspeicher des Compilers. Ein Interrupt darf währenddessen keinen weiteren Banktransfer über denselben Speicher ausführen. Auf GB nutzt die Kopierhilfe außerdem einen gemeinsamen HRAM-Platz für die gespeicherte Bank. Das Beispiel führt die Operationen aus festem Code ohne konkurrierenden Transfer-Handler aus.
FC-Banknummern sind logische Nummern des Compilers. Null bezeichnet gemeinsames ROM, kein Umschaltziel; die Hilfsroutine setzt null auf Bank 1 um. Bei SUROM512 sind nur 1–30 gültig: Eine direkt an das Umschalt-Intrinsic übergebene Konstante außerhalb des Bereichs führt zu einem Buildfehler; ein ungültiger Laufzeitwert wählt 1 und setzt den Mapper-Fehlerzustand. Lesen Sie gemeinsames ROM direkt, statt Bank null anzufordern.
Anwendungsbeispiel
bank_expect(__farpeek16(3,(u16)(payload+1))==0x5C7A,14);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Dieser Ausschnitt liest mit __farpeek16 den Ausdruck payload+1 in Bank 3 und prüft 5C7A. Das vollständige Programm bestätigt durch einen Nahzugriff auf home2 außerdem die Wiederherstellung von Bank 2.
Das vollständige Programm zeigt Datenzugriffe über Bank und Nahadresse, bankübergreifende Callbacks und den Unterschied zwischen Bibliothekszustand und tatsächlicher Zuordnung. Alle Zahlen sind hexadezimal. BANK IN und BANK OUT müssen 02 zeigen, BYTE HEX D3, WORD HEX 5C7A sowie FAILED CHECKS und FIRST FAILURE jeweils 00. CGB und FC zeigen blaue Schrift, DMG schwarze.
CALL COUNT zeigt 02: Das Makro far_call und __farcall rufen den Callback jeweils einmal auf.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\banked_data_calls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:90
Temporarily select bank and read a little-endian word, then restore the previous mapping.
kitaqfc/kitaqfc/CodeGenerator.cs:8698
EmitHelperStart("__farpeek16");
// args: bank, addr16. Return A=lo, X=hi.
EmitAsm("LDA", Mem(_runtimeCurrentBankAddress));
EmitAsm("PHA");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_prg_set_bank_a"));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("INY");
EmitAsm("LDA", IndY(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("PLA");
EmitAsm("JSR", Abs("__kq_prg_set_bank_a"));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Consume the word count while advancing source/destination pointers; zero count still restores the saved bank.__farpeek8
Wählt kurzzeitig die angegebene Bank, liest ein Byte an der CPU-Adresse und stellt die vorherige Bank wieder her. Dieses Intrinsic wird von far_data_read8 verwendet.
Syntax
u8 __farpeek8(u8 bank, u16 addr);Parameter
bankVorzeichenlose 8-Bit-ROM-Banknummer. Bei Aufrufen muss sie zur Funktionsplatzierung passen, bei Lesezugriffen wählt sie die Quelle. Verwenden Sie den gültigen Bereich des Modultyps.
addr16-Bit-CPU-Adresse der Quelldaten in der gewählten ROM-Zuordnung. Die FC-Deklaration erwartet
u16; wandeln Sie einen Ressourcenzeiger mit(u16)pointerum.
Rückgabewert
Das gelesene Byte als u8 (0–255).
Bedingungen und Grenzen
Das gesamte Byte oder Wort muss in der gewählten CPU-Zuordnung lesbar sein. Zeiger werden nicht geprüft, Adressen nicht begrenzt und Fenstergrenzen lösen keinen automatischen Wechsel zur nächsten ROM-Bank aus. Ein Wort benötigt zwei gültige aufeinanderfolgende Bytes.
Fernlesezugriffe und Kopien arbeiten synchron und nutzen gemeinsamen Arbeitsspeicher des Compilers. Ein Interrupt darf währenddessen keinen weiteren Banktransfer über denselben Speicher ausführen. Auf GB nutzt die Kopierhilfe außerdem einen gemeinsamen HRAM-Platz für die gespeicherte Bank. Das Beispiel führt die Operationen aus festem Code ohne konkurrierenden Transfer-Handler aus.
FC-Banknummern sind logische Nummern des Compilers. Null bezeichnet gemeinsames ROM, kein Umschaltziel; die Hilfsroutine setzt null auf Bank 1 um. Bei SUROM512 sind nur 1–30 gültig: Eine direkt an das Umschalt-Intrinsic übergebene Konstante außerhalb des Bereichs führt zu einem Buildfehler; ein ungültiger Laufzeitwert wählt 1 und setzt den Mapper-Fehlerzustand. Lesen Sie gemeinsames ROM direkt, statt Bank null anzufordern.
Anwendungsbeispiel
bank_expect(__farpeek8(3,(u16)payload)==0xD3,13);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Dieser Ausschnitt liest mit __farpeek8 den Ausdruck payload in Bank 3 und prüft D3. Das vollständige Programm bestätigt durch einen Nahzugriff auf home2 außerdem die Wiederherstellung von Bank 2.
Das vollständige Programm zeigt Datenzugriffe über Bank und Nahadresse, bankübergreifende Callbacks und den Unterschied zwischen Bibliothekszustand und tatsächlicher Zuordnung. Alle Zahlen sind hexadezimal. BANK IN und BANK OUT müssen 02 zeigen, BYTE HEX D3, WORD HEX 5C7A sowie FAILED CHECKS und FIRST FAILURE jeweils 00. CGB und FC zeigen blaue Schrift, DMG schwarze.
CALL COUNT zeigt 02: Das Makro far_call und __farcall rufen den Callback jeweils einmal auf.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\banked_data_calls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:88
Temporarily select bank, read one byte at addr and restore the prior bank mapping.
kitaqfc/kitaqfc/CodeGenerator.cs:8683
EmitHelperStart("__farpeek8");
// args: bank, addr16. Return A.
EmitAsm("LDA", Mem(_runtimeCurrentBankAddress));
EmitAsm("PHA");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_prg_set_bank_a"));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("PLA");
EmitAsm("JSR", Abs("__kq_prg_set_bank_a"));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Retain both fetched bytes across bank restoration and return the word in A/X.__fkb_detect
Prüfen Sie das Erkennungsmuster der Tastatur, bevor Sie Tastendrücke auswerten.
Syntax
u8 __fkb_detect(void);Rückgabewert
Liefert 1, wenn Zeile 9, Spalte 0 den Wert 0x1E und die deaktivierte Matrix null ergibt; andernfalls 0. Das ist eine Heuristik anhand eines Signalmusters und keine Gerätekennung.
Bedingungen und Grenzen
Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.
Schreibt 5 nach $4016 zum Zurücksetzen auf Zeile 0 und schaltet Zeilen über fallende Flanken des Spaltenauswahlbits weiter. Neben den umgebenden Befehlen enthält der erzeugte Code 12 CPU-Zyklen NOPs nach Reset und 50 nach der Spaltenauswahl. Beim Beenden wird null geschrieben, um die Matrix zu deaktivieren.
Ein separater Regressionstest prüft anhand ausgeführter CPU-Busspuren Zeilen 0 bis 9, beide Spalten, den Neun-Zeilen-Scan, Einschwingzeiten und den abschließenden Portzustand. Er speist keine physischen Tastensignale ein.
Zeile 9 enthält keine Tasten. Sobald eine Bedingung nicht erfüllt ist, wird null zurückgegeben. KUROSAKI liefert bereits beim ersten Lesen null, weshalb die Erkennung dort fehlschlägt.
Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.
Anwendungsbeispiel
#include "fc_ports_scene.h"
void main(void){
u8 row;
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__fkb_detect();
__fkb_scan(key_matrix);
port_result[1]=__fkb_read_row_col(6,0);
port_result[2]=0;
if(port_result[0] && !(port_result[1]&16))port_result[2]=1;
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"KEYBOARD STARTUP SNAPSHOT");m_wait();
m_text(2,4,"DEVICE DETECT");port_number(25,4,port_result[0]);m_wait();
m_text(2,6,"ROW 6 COL 0");port_number(25,6,port_result[1]);m_wait();
m_text(2,8,"A KEY - GUARDED");port_number(25,8,port_result[2]);m_wait();
m_text(2,10,"ROW COL0 COL1 (HEX)");m_wait();
for(row=0;row<9;row++){
m_put(3,12+row,'0'+row);port_hex(9,12+row,key_matrix[row*2]);port_hex(15,12+row,key_matrix[row*2+1]);m_wait();
}
m_text(2,23,"RAW MATRIX - NOT ASCII");m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
In diesem KUROSAKI-Lauf sind DEVICE DETECT, ROW 6 COL 0 und A KEY - GUARDED jeweils 000; alle neun COL0/COL1-Paare sind 00. A liegt auf Zeile 6, Spalte 0, Bit 4. Die fehlgeschlagene Erkennung verhindert aber, dass der Rohwert null als Tastendruck gemeldet wird. Die Tabelle ist eine einmalige Startaufnahme.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_keyboard.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_keyboard.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_keyboard.nes --frames 120 --png .\out\peripheral_keyboard.pngImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:259
Family BASIC Keyboard. __fkb_scan() writes 18 bytes: 9 rows x 2 columns, each byte containing raw $4017 & 0x1E for that row/column. Probe selected-row and disabled-matrix bit patterns and return 0 or 1; this is a heuristic detection.
kitaqfc/kitaqfc/CodeGenerator.cs:7842
EmitHelperStart("__fkb_detect");
EmitAsm("LDA", Imm(0x09));
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("JSR", Abs("__fkb_read_row_col"));
EmitAsm("CMP", Imm(0x1E));
string no = NewGeneratedLabel("fkb_no");
EmitAsm("BNE", Rel(no));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x1E));
EmitAsm("BNE", Rel(no));
EmitAsm("LDA", Imm(1));
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, no));
EmitAsm("LDA", Imm(0));
EmitAsm("RTS");__fkb_read_row_col
Liest eine ausgewählte Zeile und Spaltengruppe ohne 18-Byte-Puffer, wenn nur diese Gruppe benötigt wird.
Syntax
u8 __fkb_read_row_col(u8 row, u8 col);Parameter
rowVerwenden Sie 0..8 für Tastenzeilen oder 9 für die Erkennungszeile ohne Tasten. Es wird keine Bereichsprüfung zur Laufzeit erzeugt.
col0 oder 1. Nur das niederwertigste Bit wird verwendet: Gerade Werte wählen Gruppe 0, ungerade Gruppe 1.
Rückgabewert
Liefert $4017 & 0x1E, ohne die Bits auf die niederwertigen Positionen zu verschieben.
Bedingungen und Grenzen
Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.
Tastengruppen sind die Rohbits 1 bis 4: Eine gedrückte Taste löscht ihr Bit, eine ruhende Gruppe ergibt 0x1E. Dies ist weder ASCII-Text noch eine Bitmaske mit eins für gedrückt. Auch ein getrenntes Gerät kann null liefern. Deuten Sie das ohne Anwesenheitsprüfung nicht als „alle Tasten gedrückt“.
Schreibt 5 nach $4016 zum Zurücksetzen auf Zeile 0 und schaltet Zeilen über fallende Flanken des Spaltenauswahlbits weiter. Neben den umgebenden Befehlen enthält der erzeugte Code 12 CPU-Zyklen NOPs nach Reset und 50 nach der Spaltenauswahl. Beim Beenden wird null geschrieben, um die Matrix zu deaktivieren.
Ein separater Regressionstest prüft anhand ausgeführter CPU-Busspuren Zeilen 0 bis 9, beide Spalten, den Neun-Zeilen-Scan, Einschwingzeiten und den abschließenden Portzustand. Er speist keine physischen Tastensignale ein.
Setzt bei jedem Aufruf auf Zeile 0 zurück und rückt erneut vor. Die Zeilenposition des vorherigen Aufrufs wird nicht übernommen.
Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.
Anwendungsbeispiel
#include "fc_ports_scene.h"
void main(void){
u8 row;
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__fkb_detect();
__fkb_scan(key_matrix);
port_result[1]=__fkb_read_row_col(6,0);
port_result[2]=0;
if(port_result[0] && !(port_result[1]&16))port_result[2]=1;
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"KEYBOARD STARTUP SNAPSHOT");m_wait();
m_text(2,4,"DEVICE DETECT");port_number(25,4,port_result[0]);m_wait();
m_text(2,6,"ROW 6 COL 0");port_number(25,6,port_result[1]);m_wait();
m_text(2,8,"A KEY - GUARDED");port_number(25,8,port_result[2]);m_wait();
m_text(2,10,"ROW COL0 COL1 (HEX)");m_wait();
for(row=0;row<9;row++){
m_put(3,12+row,'0'+row);port_hex(9,12+row,key_matrix[row*2]);port_hex(15,12+row,key_matrix[row*2+1]);m_wait();
}
m_text(2,23,"RAW MATRIX - NOT ASCII");m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
In diesem KUROSAKI-Lauf sind DEVICE DETECT, ROW 6 COL 0 und A KEY - GUARDED jeweils 000; alle neun COL0/COL1-Paare sind 00. A liegt auf Zeile 6, Spalte 0, Bit 4. Die fehlgeschlagene Erkennung verhindert aber, dass der Rohwert null als Tastendruck gemeldet wird. Die Tabelle ist eine einmalige Startaufnahme.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_keyboard.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_keyboard.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_keyboard.nes --frames 120 --png .\out\peripheral_keyboard.pngImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:264
Select a matrix row and the low bit of col, then return raw 4017 & 1E. Use rows 0..8 for keys; row 9 is used by detection. No row-range validation is emitted.
kitaqfc/kitaqfc/CodeGenerator.cs:7802
EmitHelperStart("__fkb_read_row_col");
EmitAsm("LDA", Imm(0x05));
EmitAsm("STA", Mem(0x4016));
EmitKeyboardDelayShort();
// Establish row zero, then generate one high-to-low column edge
// per requested advance. Repeated writes of 04 do not clock the counter.
EmitAsm("LDA", Imm(0x04));
EmitAsm("STA", Mem(0x4016));
EmitKeyboardDelayLong();
EmitAsm("LDX", Mem(CallArgBase));
string selectedRow = NewGeneratedLabel("fkb_selected_row");
EmitAsm("BEQ", Rel(selectedRow));
string adv = NewGeneratedLabel("fkb_adv_row");
_assembly.Add(Expr.Make(Tag.Label, adv));
EmitAsm("LDA", Imm(0x06));
EmitAsm("STA", Mem(0x4016));
EmitKeyboardDelayLong();
EmitAsm("LDA", Imm(0x04));
EmitAsm("STA", Mem(0x4016));
EmitKeyboardDelayLong();
EmitAsm("DEX");
EmitAsm("BNE", Rel(adv));
_assembly.Add(Expr.Make(Tag.Label, selectedRow));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(1));
string readNow = NewGeneratedLabel("fkb_read_now");
EmitAsm("BEQ", Rel(readNow));
EmitAsm("LDA", Imm(0x06));
EmitAsm("STA", Mem(0x4016));
EmitKeyboardDelayLong();
_assembly.Add(Expr.Make(Tag.Label, readNow));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x1E));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");__fkb_scan
Liest alle neun Zeilen mit jeweils zwei Spaltengruppen in 18 Bytes ein. So können Sie mehrere Tasten anhand derselben Matrixabfrage prüfen.
Syntax
void __fkb_scan(u8* dst18);Parameter
dst18Beschreibbarer Speicher mit mindestens 18 Bytes. Element
row*2+colerhält den Rohwert der jeweiligen Gruppe. Nullzeiger und Länge werden nicht geprüft.
Rückgabewert
Kein Rückgabewert.
Bedingungen und Grenzen
Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.
Tastengruppen sind die Rohbits 1 bis 4: Eine gedrückte Taste löscht ihr Bit, eine ruhende Gruppe ergibt 0x1E. Dies ist weder ASCII-Text noch eine Bitmaske mit eins für gedrückt. Auch ein getrenntes Gerät kann null liefern. Deuten Sie das ohne Anwesenheitsprüfung nicht als „alle Tasten gedrückt“.
Schreibt 5 nach $4016 zum Zurücksetzen auf Zeile 0 und schaltet Zeilen über fallende Flanken des Spaltenauswahlbits weiter. Neben den umgebenden Befehlen enthält der erzeugte Code 12 CPU-Zyklen NOPs nach Reset und 50 nach der Spaltenauswahl. Beim Beenden wird null geschrieben, um die Matrix zu deaktivieren.
Ein separater Regressionstest prüft anhand ausgeführter CPU-Busspuren Zeilen 0 bis 9, beide Spalten, den Neun-Zeilen-Scan, Einschwingzeiten und den abschließenden Portzustand. Er speist keine physischen Tastensignale ein.
Liest in jeder Zeile zuerst Spalte 0, dann Spalte 1. Die Abfrage erfolgt nacheinander und erfasst nicht alle Tasten zum selben Zeitpunkt. Eine Gruppe mit dem Wert null beendet den Durchlauf nicht vorzeitig.
Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.
Anwendungsbeispiel
#include "fc_ports_scene.h"
void main(void){
u8 row;
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__fkb_detect();
__fkb_scan(key_matrix);
port_result[1]=__fkb_read_row_col(6,0);
port_result[2]=0;
if(port_result[0] && !(port_result[1]&16))port_result[2]=1;
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"KEYBOARD STARTUP SNAPSHOT");m_wait();
m_text(2,4,"DEVICE DETECT");port_number(25,4,port_result[0]);m_wait();
m_text(2,6,"ROW 6 COL 0");port_number(25,6,port_result[1]);m_wait();
m_text(2,8,"A KEY - GUARDED");port_number(25,8,port_result[2]);m_wait();
m_text(2,10,"ROW COL0 COL1 (HEX)");m_wait();
for(row=0;row<9;row++){
m_put(3,12+row,'0'+row);port_hex(9,12+row,key_matrix[row*2]);port_hex(15,12+row,key_matrix[row*2+1]);m_wait();
}
m_text(2,23,"RAW MATRIX - NOT ASCII");m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
In diesem KUROSAKI-Lauf sind DEVICE DETECT, ROW 6 COL 0 und A KEY - GUARDED jeweils 000; alle neun COL0/COL1-Paare sind 00. A liegt auf Zeile 6, Spalte 0, Bit 4. Die fehlgeschlagene Erkennung verhindert aber, dass der Rohwert null als Tastendruck gemeldet wird. Die Tabelle ist eine einmalige Startaufnahme.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_keyboard.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_keyboard.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_keyboard.nes --frames 120 --png .\out\peripheral_keyboard.pngImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:261
Write 18 raw masked samples (4017 & 1E), two per row, to caller storage; reset 4016 afterward.
kitaqfc/kitaqfc/CodeGenerator.cs:7773
EmitHelperStart("__fkb_scan");
EmitAsm("LDA", Imm(0x05));
EmitAsm("STA", Mem(0x4016));
EmitKeyboardDelayShort();
EmitAsm("LDY", Imm(0));
EmitAsm("LDX", Imm(9));
string rowLoop = NewGeneratedLabel("fkb_scan_row");
_assembly.Add(Expr.Make(Tag.Label, rowLoop));
EmitAsm("LDA", Imm(0x04));
EmitAsm("STA", Mem(0x4016));
EmitKeyboardDelayLong();
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x1E));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INY");
EmitAsm("LDA", Imm(0x06));
EmitAsm("STA", Mem(0x4016));
EmitKeyboardDelayLong();
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x1E));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INY");
EmitAsm("DEX");
EmitAsm("BNE", Rel(rowLoop));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
EmitAsm("RTS");__irq_disable
Erzeugt SEI, setzt damit das I-Bit im CPU-Statusregister und maskiert normale IRQ-Anforderungen. Geeignet für einen gegen IRQ geschützten Abschnitt. NMI, APU-Tonausgabe und IRQ-Quelle bleiben unverändert; die vorherige Maske wird nicht gespeichert.
Syntax
void __irq_disable(void);Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Das I-Bit steuert nur normale IRQ: NMI kann diesen Abschnitt weiterhin unterbrechen. Eine IRQ-Sperre quittiert keine Geräteanforderung und schützt keine mit NMI geteilten Daten. Quellen und Handler separat vorbereiten. Das Beispiel testet kein IRQ-erzeugendes Peripheriegerät.
Anwendungsbeispiel
__irq_disable();
disabled=(__irq_save()&4)>>2; // The CPU I bit must be 1.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm unterscheidet zwischen der IRQ-Maske der CPU und der NMI-Steuerung der PPU. Blau auf Schwarz müssen IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 und RESTORED I = 000 erscheinen. Die Werte zeigen nur das extrahierte I-Bit, nicht das ganze Statusbyte. FAILED CHECKS muss 000 sein.
Mindestens 360 Emulatorframes ausführen, damit die Demonstration mit 256 Warteaufrufen abgeschlossen wird. NMI DELTA 3 WAITS muss trotz IRQ-Sperre 003 ergeben; NMI DELTA 256 WAITS wird durch den Byteumlauf zu 000. NMI OFF DELTA muss ebenfalls 000 sein. Das Programm verwendet den Standardhandler ohne runtime.c. Weitere 21 Zustandsprüfungen erfassen unter anderem die Wiederherstellung der Statusbits sowie zeitlich begrenzt beobachtete Wartefälle bei deaktivierter NMI oder leerem eigenem Handler. Geprüft wurde KUROSAKI/NROM, keine echte Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\interrupt_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\interrupt_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:176
Emit SEI to mask CPU IRQs; NMI is unaffected.
kitaqfc/kitaqfc/CodeGenerator.cs:4733
case "__irq_disable": if (!Need(0)) return true; EmitAsm("SEI"); return true;__irq_enable
Erzeugt CLI, löscht das I-Bit im CPU-Statusregister und erlaubt die Bearbeitung normaler IRQ-Anforderungen. Eine Interruptquelle im Mapper oder in der APU wird nicht eingerichtet. Bereiten Sie vor deren Aktivierung den Handler und die Quittierung der IRQ-Anforderung vor.
Syntax
void __irq_enable(void);Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Das I-Bit steuert nur normale IRQ: NMI kann diesen Abschnitt weiterhin unterbrechen. Eine IRQ-Sperre quittiert keine Geräteanforderung und schützt keine mit NMI geteilten Daten. Quellen und Handler separat vorbereiten. Das Beispiel testet kein IRQ-erzeugendes Peripheriegerät.
Anwendungsbeispiel
__irq_enable();
enabled=(__irq_save()&4)>>2; // Save returns the old I bit: 0, then masks IRQ.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm unterscheidet zwischen der IRQ-Maske der CPU und der NMI-Steuerung der PPU. Blau auf Schwarz müssen IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 und RESTORED I = 000 erscheinen. Die Werte zeigen nur das extrahierte I-Bit, nicht das ganze Statusbyte. FAILED CHECKS muss 000 sein.
Mindestens 360 Emulatorframes ausführen, damit die Demonstration mit 256 Warteaufrufen abgeschlossen wird. NMI DELTA 3 WAITS muss trotz IRQ-Sperre 003 ergeben; NMI DELTA 256 WAITS wird durch den Byteumlauf zu 000. NMI OFF DELTA muss ebenfalls 000 sein. Das Programm verwendet den Standardhandler ohne runtime.c. Weitere 21 Zustandsprüfungen erfassen unter anderem die Wiederherstellung der Statusbits sowie zeitlich begrenzt beobachtete Wartefälle bei deaktivierter NMI oder leerem eigenem Handler. Geprüft wurde KUROSAKI/NROM, keine echte Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\interrupt_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\interrupt_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:178
Emit CLI to unmask CPU IRQs; the caller must have a valid interrupt handler.
kitaqfc/kitaqfc/CodeGenerator.cs:4734
case "__irq_enable": if (!Need(0)) return true; EmitAsm("CLI"); return true;
// Return the saved processor status in A after masking IRQs; restore consumes a status byte, not a Boolean.__irq_restore
Wertet das übergebene Statusbyte aus, legt es mit PHA auf den Stack und stellt den CPU-Status mit PLP wieder her. Dabei werden die gespeicherte IRQ-Maske und die Rechenflags übernommen; IRQ wird nicht bedingungslos freigegeben. Nachfolgende C-Ausdrücke können die Rechenflags erneut verändern.
Syntax
void __irq_restore(u8 state);Parameter
stateDer vollständige, von __irq_save erfasste 8-Bit-CPU-Status. Für __irq_restore aufbewahren, nicht in einen booleschen Wert umwandeln. Bit 2 ist I: 1 sperrt IRQ, 0 erlaubt IRQ. Die von PHP abgelegte Darstellung setzt die Bits 4 und 5.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Das I-Bit steuert nur normale IRQ: NMI kann diesen Abschnitt weiterhin unterbrechen. Eine IRQ-Sperre quittiert keine Geräteanforderung und schützt keine mit NMI geteilten Daten. Quellen und Handler separat vorbereiten. Das Beispiel testet kein IRQ-erzeugendes Peripheriegerät.
Anwendungsbeispiel
__irq_restore(saved_state); // Restore the caller's original IRQ mask.
restored=(__irq_save()&4)>>2; // Original I was 0; this probe masks it again.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm unterscheidet zwischen der IRQ-Maske der CPU und der NMI-Steuerung der PPU. Blau auf Schwarz müssen IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 und RESTORED I = 000 erscheinen. Die Werte zeigen nur das extrahierte I-Bit, nicht das ganze Statusbyte. FAILED CHECKS muss 000 sein.
Mindestens 360 Emulatorframes ausführen, damit die Demonstration mit 256 Warteaufrufen abgeschlossen wird. NMI DELTA 3 WAITS muss trotz IRQ-Sperre 003 ergeben; NMI DELTA 256 WAITS wird durch den Byteumlauf zu 000. NMI OFF DELTA muss ebenfalls 000 sein. Das Programm verwendet den Standardhandler ohne runtime.c. Weitere 21 Zustandsprüfungen erfassen unter anderem die Wiederherstellung der Statusbits sowie zeitlich begrenzt beobachtete Wartefälle bei deaktivierter NMI oder leerem eigenem Handler. Geprüft wurde KUROSAKI/NROM, keine echte Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\interrupt_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\interrupt_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:182
Restore processor status with PLP, including arithmetic flags and the saved IRQ mask.
kitaqfc/kitaqfc/CodeGenerator.cs:4737
case "__irq_restore": if (!Need(1)) return true; Load1(args[0]); EmitAsm("PHA"); EmitAsm("PLP"); return true;__irq_save
Sichert das CPU-Statusbyte mit PHP, maskiert IRQ mit SEI und gibt das gesicherte Byte über PLA zurück. Das gespeicherte I-Bit beschreibt den Zustand vor der Maskierung. Mit __irq_restore bleibt auch bei verschachtelten kritischen Abschnitten eine zuvor aktive IRQ-Sperre erhalten.
Syntax
u8 __irq_save(void);Rückgabewert
Der vollständige, von __irq_save erfasste 8-Bit-CPU-Status. Für __irq_restore aufbewahren, nicht in einen booleschen Wert umwandeln. Bit 2 ist I: 1 sperrt IRQ, 0 erlaubt IRQ. Die von PHP abgelegte Darstellung setzt die Bits 4 und 5.
Bedingungen und Grenzen
Das I-Bit steuert nur normale IRQ: NMI kann diesen Abschnitt weiterhin unterbrechen. Eine IRQ-Sperre quittiert keine Geräteanforderung und schützt keine mit NMI geteilten Daten. Quellen und Handler separat vorbereiten. Das Beispiel testet kein IRQ-erzeugendes Peripheriegerät.
Anwendungsbeispiel
saved_state=__irq_save(); // Save all processor-status bits, then set I.
inside=(__irq_save()&4)>>2; // IRQ remains masked inside this section.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm unterscheidet zwischen der IRQ-Maske der CPU und der NMI-Steuerung der PPU. Blau auf Schwarz müssen IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 und RESTORED I = 000 erscheinen. Die Werte zeigen nur das extrahierte I-Bit, nicht das ganze Statusbyte. FAILED CHECKS muss 000 sein.
Mindestens 360 Emulatorframes ausführen, damit die Demonstration mit 256 Warteaufrufen abgeschlossen wird. NMI DELTA 3 WAITS muss trotz IRQ-Sperre 003 ergeben; NMI DELTA 256 WAITS wird durch den Byteumlauf zu 000. NMI OFF DELTA muss ebenfalls 000 sein. Das Programm verwendet den Standardhandler ohne runtime.c. Weitere 21 Zustandsprüfungen erfassen unter anderem die Wiederherstellung der Statusbits sowie zeitlich begrenzt beobachtete Wartefälle bei deaktivierter NMI oder leerem eigenem Handler. Geprüft wurde KUROSAKI/NROM, keine echte Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\interrupt_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\interrupt_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:180
Return the processor-status byte captured before SEI. Pass that byte to __irq_restore.
kitaqfc/kitaqfc/CodeGenerator.cs:4736
case "__irq_save": if (!Need(0)) return true; EmitAsm("PHP"); EmitAsm("SEI"); EmitAsm("PLA"); return true;__irq_scanline_set
Alias des MMC3-Latch-/Nachladevorgangs in __mapper_irq_set. Das Wort scanline garantiert keine absolute Bildschirmzeile.
Syntax
void __irq_scanline_set(u8 line);Parameter
lineAcht-Bit-Latchwert des Zählers von 0 bis 255, keine absolute Bildschirm-Y-Koordinate. Nachladezeitpunkt und qualifizierte PPU-A12-Übergänge bestimmen die Auslösung. Null ist gültig und bedeutet nicht „Interrupts deaktivieren“.
Rückgabewert
Kein Rückgabewert.
Bedingungen und Grenzen
Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.
Nur MMC3. Erforderlich sind ein __nes_irq-Handler, qualifizierte PPU-A12-Übergänge, Mapper-IRQ-Freigabe und CPU-__irq_enable(). Das Beispiel aktiviert die Darstellung mit Hintergrundmustern bei $0000 und Spritemustern bei $1000. Die Lektion prüft keine Synchronisierung auf eine exakte Zeile echter Hardware.
MMC3-Quittierung und -Deaktivierung schreiben beide nach $E000. Das löscht die aktuelle Anforderung und deaktiviert weitere. Rufen Sie zum gewünschten Zeitpunkt erneut __mapper_irq_enable() auf, um einen weiteren Interrupt freizugeben. Beide Vorgänge ändern weder CPU-Interruptmaske noch NMI-Freigabe.
Der Compiler erzeugt RTI für eigene Interrupt-Handler. Der Handler muss aber alle von ihm geänderten A/X/Y-Register und gemeinsamen temporären Speicher sichern. Das Beispiel sichert A/X/Y und verwendet nur ein Byteinkrement sowie eine argumentlose Quittierung. Prüfen Sie bei zusätzlichen C-Aufrufen auch deren gemeinsam genutzten Speicher.
Anwendungsbeispiel
irq_phase=2;
__irq_scanline_set(7); __mapper_irq_enable();
for (i=0;i<6;i++) { m_wait(); }
mapper_result[17]=irq_count;Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:324
Write the MMC3 IRQ latch and reload registers. This does not enable IRQ delivery.
__joypad2p_voice
__joypad2p_voice ist ein weiterer Aufrufname für die Routine __mic_read2p. Beide lesen dasselbe digitale Mikrofonbit. „Voice“ bezeichnet keine Audioverarbeitung: Jeder Aufruf liest einen neuen Wert, der sich bei einer Signaländerung zwischen den Aufrufen unterscheiden kann.
Liest das digitale Mikrofonsignal des zweiten Famicom-Controllers aus Bit 2 von $4016. Die Routine liest das Register einmal, maskiert mit 0x04 und normalisiert ohne Invertierung auf 0 oder 1. Sie übernimmt keine Controllerzustände, zeichnet kein Audio auf, misst keine Lautstärke und erkennt keine Sprache.
Syntax
u8 __joypad2p_voice(void);Rückgabewert
Genau 1, wenn $4016 & 0x04 ungleich null ist, andernfalls 0. Dies ist der gelesene Digitalpegel, keine Tastenmaske, Amplitude, Frequenz oder Anzahl gesprochener Wörter.
Bedingungen und Grenzen
Das Mikrofon gehört zu Controller 2, die Routine greift jedoch auf $4016 statt $4017 zu. Ein Registerzugriff kann bei niedrigem Strobe auch den seriellen Controllerstrom weiterschalten. Fügen Sie daher keine Mikrofonzugriffe in die Achtbit-Leseschleife eines anderen Controllers ein. Ein gespeicherter Eingabeverlauf wird nicht aktualisiert.
Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.
Anwendungsbeispiel
voice=__joypad2p_voice();Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Dieses Programm vergleicht normale D0-Eingaben mit D1 und dem digitalen Mikrofonpegel. Halten Sie A auf Controller 1 und B auf Controller 2 bis zur Erfassung gedrückt. NORMAL P1 und NORMAL P2 zeigen 001 und 002. Im beigefügten KUROSAKI-Lauf zeigen D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL und MIC DIRECT jeweils 000, weil diese Leitungen nicht emuliert werden. Die erfassten Werte bleiben anschließend stehen. Mit geeigneten Peripheriegeräten und implementierten Signalleitungen hängen die Werte von den tatsächlichen Eingaben ab; deuten Sie sie anhand der obigen Bitbelegung, statt diese Nullen zu erwarten.
Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\expansion_input.c -I .\kitaq-docs\samples -I .\kitaqfc\lib -o .\out\expansion_input.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:231
Return controller-port 4016 bit 2 normalized to 0 or 1; this is a microphone level bit, not PCM audio.
__mac16
Berechnet eine vorzeichenlose Multiplikation mit Akkumulation.
Syntax
u16 __mac16(u16 acc, u16 a, u8 b);Parameter
accZu addierender 16-Bit-Akkumulator.
aVorzeichenloses Wort, 0..65535.
bVorzeichenloser Koeffizient, 0..255.
Rückgabewert
Gibt die unteren 16 Bits von acc+a*b zurück.
Bedingungen und Grenzen
Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.
Anwendungsbeispiel
// Compare literal coefficients with values read from RAM.
result[8]=__mac16(65500,513,127);
v0=65500;v1=513;v2=127;
result[9]=__mac16(v0,v1,v2);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.
Der Ausschnitt speichert folgende 16-Bit-Werte: result[8]=65115, result[9]=65115. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chrImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:55
Return acc+a*b modulo 65536 using unsigned multiplication.
kitaqfc/kitaqfc/CodeGenerator.cs:4650
case "__mac16": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__manhattan
Addiert die absoluten X- und Y-Differenzen zweier Punkte.
Syntax
u8 __manhattan(u8 x1, u8 y1, u8 x2, u8 y2);Parameter
x1X-Koordinate des ersten Punkts, 0..255.
y1Y-Koordinate des ersten Punkts, 0..255.
x2X-Koordinate des zweiten Punkts, 0..255.
y2Y-Koordinate des zweiten Punkts, 0..255.
Rückgabewert
Gibt das untere Byte von |x1−x2|+|y1−y2| zurück; Distanzen über 255 laufen um.
Anwendungsbeispiel
result[5]=__manhattan(2,3,9,11);result[6]=__manhattan(0,0,255,255);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Prüfen Sie Innenraum, Grenzen, einen äußeren Punkt und Breite null: INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1. Prüfen Sie Distanz 15, umgelaufene Distanz 254 und Array-Indizes 64/65280.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_coordinates.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_coordinates.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chrImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:33
Return abs(x1-x2)+abs(y1-y2) modulo 256; large distances are not saturated.
kitaqfc/kitaqfc/CodeGenerator.cs:4629
case "__manhattan":
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1,1}, "__manhattan");
void EmitHelperGeometryAndRng()
{
EmitHelperStart("__xy_in_rect");
string xyFalse = NewGeneratedLabel("xy_in_rect_false");
string xyDone = NewGeneratedLabel("xy_in_rect_done");
// return (x-rx < rw) && (y-ry < rh), using unsigned subtract to avoid rx+rw overflow.
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(xyFalse));
EmitAsm("CMP", Mem(CallArgBase + 4));
EmitAsm("BCS", Rel(xyFalse));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 3));
EmitAsm("BCC", Rel(xyFalse));
EmitAsm("CMP", Mem(CallArgBase + 5));
EmitAsm("BCS", Rel(xyFalse));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(xyDone));
_assembly.Add(Expr.Make(Tag.Label, xyFalse));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, xyDone));
EmitAsm("RTS");
EmitHelperStart("__manhattan");
// dx = abs(x1-x2)
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 2));
string mdxOk = NewGeneratedLabel("manhattan_dx_ok");
EmitAsm("BCS", Rel(mdxOk));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("CLC");
EmitAsm("ADC", Imm(1));
_assembly.Add(Expr.Make(Tag.Label, mdxOk));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
// dy = abs(y1-y2), then add dx.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 3));
string mdyOk = NewGeneratedLabel("manhattan_dy_ok");
EmitAsm("BCS", Rel(mdyOk));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("CLC");
EmitAsm("ADC", Imm(1));
_assembly.Add(Expr.Make(Tag.Label, mdyOk));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Save X-coordinate separately while multiplying width by Y, then add X with carry into the high byte.
EmitHelperStart("__map_index");
// args: x, y, width. Return y * width + x as u16.
EmitAsm("LDA", Mem(CallArgBase)); // x
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Mem(CallArgBase + 1)); // y
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 2)); // width as u16 multiplicand
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("TXA");
EmitAsm("ADC", Imm(0));
EmitAsm("TAX");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Replace both bytes of the persistent generator state with the supplied seed.
EmitHelperStart("__rng_seed");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeRngLoAddress));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
EmitAsm("RTS");
EmitHelperStart("__rng8");
// 16-bit Galois LFSR: if low bit set, shift right then xor $B400.
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("ORA", Mem(_runtimeRngHiAddress));
// Replace the all-zero LFSR state with a deterministic nonzero seed before advancing.
string seeded = NewGeneratedLabel("rng_seeded");
EmitAsm("BNE", Rel(seeded));
EmitAsm("LDA", Imm(0x5A));
EmitAsm("STA", Mem(_runtimeRngLoAddress));
EmitAsm("LDA", Imm(0xA5));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
_assembly.Add(Expr.Make(Tag.Label, seeded));
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("AND", Imm(1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LSR", Mem(_runtimeRngHiAddress));
EmitAsm("ROR", Mem(_runtimeRngLoAddress));
string rngNoXor = NewGeneratedLabel("rng_no_xor");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("BEQ", Rel(rngNoXor));
EmitAsm("LDA", Mem(_runtimeRngHiAddress));
EmitAsm("EOR", Imm(0xB4));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
_assembly.Add(Expr.Make(Tag.Label, rngNoXor));
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("EOR", Mem(_runtimeRngHiAddress));
EmitAsm("RTS");
}__map_index
Wandelt eine Zellkoordinate in einen zeilenweisen Array-Index um.
Syntax
u16 __map_index(u8 x, u8 y, u8 width);Parameter
xSpalte 0..255, gewöhnlich kleiner als width.
yZeile, 0..255.
widthZellen je Zeile, 0..255.
Rückgabewert
Gibt y*width+x als u16 zurück; prüft die Koordinaten nicht.
Anwendungsbeispiel
result[7]=__map_index(4,3,20);result[8]=__map_index(255,255,255);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Prüfen Sie Innenraum, Grenzen, einen äußeren Punkt und Breite null: INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1. Prüfen Sie Distanz 15, umgelaufene Distanz 254 und Array-Indizes 64/65280.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_coordinates.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_coordinates.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chrImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:35
Return y*width+x as a word without checking coordinates against map dimensions.
kitaqfc/kitaqfc/CodeGenerator.cs:4631
case "__map_index":
if (TryEmitMapIndexInline(args, ctx)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1}, "__map_index");
void EmitHelperGeometryAndRng()
{
EmitHelperStart("__xy_in_rect");
string xyFalse = NewGeneratedLabel("xy_in_rect_false");
string xyDone = NewGeneratedLabel("xy_in_rect_done");
// return (x-rx < rw) && (y-ry < rh), using unsigned subtract to avoid rx+rw overflow.
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(xyFalse));
EmitAsm("CMP", Mem(CallArgBase + 4));
EmitAsm("BCS", Rel(xyFalse));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 3));
EmitAsm("BCC", Rel(xyFalse));
EmitAsm("CMP", Mem(CallArgBase + 5));
EmitAsm("BCS", Rel(xyFalse));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(xyDone));
_assembly.Add(Expr.Make(Tag.Label, xyFalse));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, xyDone));
EmitAsm("RTS");
EmitHelperStart("__manhattan");
// dx = abs(x1-x2)
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 2));
string mdxOk = NewGeneratedLabel("manhattan_dx_ok");
EmitAsm("BCS", Rel(mdxOk));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("CLC");
EmitAsm("ADC", Imm(1));
_assembly.Add(Expr.Make(Tag.Label, mdxOk));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
// dy = abs(y1-y2), then add dx.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 3));
string mdyOk = NewGeneratedLabel("manhattan_dy_ok");
EmitAsm("BCS", Rel(mdyOk));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("CLC");
EmitAsm("ADC", Imm(1));
_assembly.Add(Expr.Make(Tag.Label, mdyOk));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Save X-coordinate separately while multiplying width by Y, then add X with carry into the high byte.
EmitHelperStart("__map_index");
// args: x, y, width. Return y * width + x as u16.
EmitAsm("LDA", Mem(CallArgBase)); // x
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Mem(CallArgBase + 1)); // y
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 2)); // width as u16 multiplicand
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("TXA");
EmitAsm("ADC", Imm(0));
EmitAsm("TAX");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Replace both bytes of the persistent generator state with the supplied seed.
EmitHelperStart("__rng_seed");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeRngLoAddress));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
EmitAsm("RTS");
EmitHelperStart("__rng8");
// 16-bit Galois LFSR: if low bit set, shift right then xor $B400.
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("ORA", Mem(_runtimeRngHiAddress));
// Replace the all-zero LFSR state with a deterministic nonzero seed before advancing.
string seeded = NewGeneratedLabel("rng_seeded");
EmitAsm("BNE", Rel(seeded));
EmitAsm("LDA", Imm(0x5A));
EmitAsm("STA", Mem(_runtimeRngLoAddress));
EmitAsm("LDA", Imm(0xA5));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
_assembly.Add(Expr.Make(Tag.Label, seeded));
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("AND", Imm(1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LSR", Mem(_runtimeRngHiAddress));
EmitAsm("ROR", Mem(_runtimeRngLoAddress));
string rngNoXor = NewGeneratedLabel("rng_no_xor");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("BEQ", Rel(rngNoXor));
EmitAsm("LDA", Mem(_runtimeRngHiAddress));
EmitAsm("EOR", Imm(0xB4));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
_assembly.Add(Expr.Make(Tag.Label, rngNoXor));
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("EOR", Mem(_runtimeRngHiAddress));
EmitAsm("RTS");
}__mapper_id
Gibt die beim Build gewählte Mappernummer für platinenspezifische Initialisierung oder Anzeige zurück.
Syntax
u8 __mapper_id(void);Rückgabewert
Acht-Bit-Mappernummer: NROM=0, MMC1=1, UxROM=2, CNROM=3, MMC3=4, MMC5=5, AxROM=7, VRC6=24, FME7=69 und VRC7=85. Der Compiler erzeugt eine Konstante; laufende Hardware wird nicht untersucht.
Bedingungen und Grenzen
Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.
Anwendungsbeispiel
mapper_result[0] = __mapper_id();Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:314
Mapper Return the low byte of the mapper number selected for this build; no cartridge probe is performed.
__mapper_irq_ack
Quittiert MMC3 im IRQ-Handler. Da nach $E000 geschrieben wird, werden auch weitere IRQ-Anforderungen deaktiviert.
Syntax
void __mapper_irq_ack(void);Rückgabewert
Kein Rückgabewert.
Bedingungen und Grenzen
Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.
Nur MMC3. Erforderlich sind ein __nes_irq-Handler, qualifizierte PPU-A12-Übergänge, Mapper-IRQ-Freigabe und CPU-__irq_enable(). Das Beispiel aktiviert die Darstellung mit Hintergrundmustern bei $0000 und Spritemustern bei $1000. Die Lektion prüft keine Synchronisierung auf eine exakte Zeile echter Hardware.
MMC3-Quittierung und -Deaktivierung schreiben beide nach $E000. Das löscht die aktuelle Anforderung und deaktiviert weitere. Rufen Sie zum gewünschten Zeitpunkt erneut __mapper_irq_enable() auf, um einen weiteren Interrupt freizugeben. Beide Vorgänge ändern weder CPU-Interruptmaske noch NMI-Freigabe.
Der Compiler erzeugt RTI für eigene Interrupt-Handler. Der Handler muss aber alle von ihm geänderten A/X/Y-Register und gemeinsamen temporären Speicher sichern. Das Beispiel sichert A/X/Y und verwendet nur ein Byteinkrement sowie eine argumentlose Quittierung. Prüfen Sie bei zusätzlichen C-Aufrufen auch deren gemeinsam genutzten Speicher.
Anwendungsbeispiel
void __nes_irq(void) {
__asm {
PHA
TXA
PHA
TYA
PHA
}
irq_count++;
if (irq_phase == 1) { nes_mapper_irq_ack(); }
else { __mapper_irq_ack(); }
__asm {
PLA
TAY
PLA
TAX
PLA
}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:332
Use the same E000 write as disable; explicitly re-enable when another interrupt is needed.
__mapper_irq_disable
Schreibt nach $E000, löscht damit die MMC3-IRQ-Anforderung und deaktiviert weitere Anforderungen.
Syntax
void __mapper_irq_disable(void);Rückgabewert
Kein Rückgabewert.
Bedingungen und Grenzen
Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.
Nur MMC3. Erforderlich sind ein __nes_irq-Handler, qualifizierte PPU-A12-Übergänge, Mapper-IRQ-Freigabe und CPU-__irq_enable(). Das Beispiel aktiviert die Darstellung mit Hintergrundmustern bei $0000 und Spritemustern bei $1000. Die Lektion prüft keine Synchronisierung auf eine exakte Zeile echter Hardware.
MMC3-Quittierung und -Deaktivierung schreiben beide nach $E000. Das löscht die aktuelle Anforderung und deaktiviert weitere. Rufen Sie zum gewünschten Zeitpunkt erneut __mapper_irq_enable() auf, um einen weiteren Interrupt freizugeben. Beide Vorgänge ändern weder CPU-Interruptmaske noch NMI-Freigabe.
Der Compiler erzeugt RTI für eigene Interrupt-Handler. Der Handler muss aber alle von ihm geänderten A/X/Y-Register und gemeinsamen temporären Speicher sichern. Das Beispiel sichert A/X/Y und verwendet nur ein Byteinkrement sowie eine argumentlose Quittierung. Prüfen Sie bei zusätzlichen C-Aufrufen auch deren gemeinsam genutzten Speicher.
Anwendungsbeispiel
__irq_disable(); __mapper_irq_disable();Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:330
Write MMC3 E000 to disable and acknowledge mapper IRQs.
__mapper_irq_enable
Schreibt nach $E001, damit MMC3 bei erfüllter Zählerbedingung IRQ anfordern kann. Setzt weder das Latch noch eine Nachladeanforderung.
Syntax
void __mapper_irq_enable(void);Rückgabewert
Kein Rückgabewert.
Bedingungen und Grenzen
Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.
Nur MMC3. Erforderlich sind ein __nes_irq-Handler, qualifizierte PPU-A12-Übergänge, Mapper-IRQ-Freigabe und CPU-__irq_enable(). Das Beispiel aktiviert die Darstellung mit Hintergrundmustern bei $0000 und Spritemustern bei $1000. Die Lektion prüft keine Synchronisierung auf eine exakte Zeile echter Hardware.
MMC3-Quittierung und -Deaktivierung schreiben beide nach $E000. Das löscht die aktuelle Anforderung und deaktiviert weitere. Rufen Sie zum gewünschten Zeitpunkt erneut __mapper_irq_enable() auf, um einen weiteren Interrupt freizugeben. Beide Vorgänge ändern weder CPU-Interruptmaske noch NMI-Freigabe.
Der Compiler erzeugt RTI für eigene Interrupt-Handler. Der Handler muss aber alle von ihm geänderten A/X/Y-Register und gemeinsamen temporären Speicher sichern. Das Beispiel sichert A/X/Y und verwendet nur ein Byteinkrement sowie eine argumentlose Quittierung. Prüfen Sie bei zusätzlichen C-Aufrufen auch deren gemeinsam genutzten Speicher.
Anwendungsbeispiel
__mapper_irq_set(37); __mapper_irq_enable(); __irq_enable();
for (i=0;i<6;i++) { m_wait(); }
mapper_result[15]=irq_count;Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:328
Write MMC3 E001. The CPU IRQ mask and a valid handler are separate caller responsibilities.
__mapper_irq_set
Schreibt das MMC3-IRQ-Latch bei $C000 und fordert über $C001 das nächste Nachladen an. Die Interruptfreigabe erfolgt separat.
Syntax
void __mapper_irq_set(u8 scanline);Parameter
scanlineAcht-Bit-Latchwert des Zählers von 0 bis 255, keine absolute Bildschirm-Y-Koordinate. Nachladezeitpunkt und qualifizierte PPU-A12-Übergänge bestimmen die Auslösung. Null ist gültig und bedeutet nicht „Interrupts deaktivieren“.
Rückgabewert
Kein Rückgabewert.
Bedingungen und Grenzen
Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.
Nur MMC3. Erforderlich sind ein __nes_irq-Handler, qualifizierte PPU-A12-Übergänge, Mapper-IRQ-Freigabe und CPU-__irq_enable(). Das Beispiel aktiviert die Darstellung mit Hintergrundmustern bei $0000 und Spritemustern bei $1000. Die Lektion prüft keine Synchronisierung auf eine exakte Zeile echter Hardware.
MMC3-Quittierung und -Deaktivierung schreiben beide nach $E000. Das löscht die aktuelle Anforderung und deaktiviert weitere. Rufen Sie zum gewünschten Zeitpunkt erneut __mapper_irq_enable() auf, um einen weiteren Interrupt freizugeben. Beide Vorgänge ändern weder CPU-Interruptmaske noch NMI-Freigabe.
Der Compiler erzeugt RTI für eigene Interrupt-Handler. Der Handler muss aber alle von ihm geänderten A/X/Y-Register und gemeinsamen temporären Speicher sichern. Das Beispiel sichert A/X/Y und verwendet nur ein Byteinkrement sowie eine argumentlose Quittierung. Prüfen Sie bei zusätzlichen C-Aufrufen auch deren gemeinsam genutzten Speicher.
Anwendungsbeispiel
__mapper_irq_set(37); __mapper_irq_enable(); __irq_enable();
for (i=0;i<6;i++) { m_wait(); }
mapper_result[15]=irq_count;Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das MMC3-Beispiel zeigt CHR-Marker 19/20, 21/22 und 23/24. CIRAM-Lesewerte ergeben A B A B bei vertikaler Spiegelung und A A B B bei horizontaler. Die IRQ-Zählerstände sind 01→02→03→03: Jede der ersten drei ausdrücklichen Freigaben erlaubt einen Interrupt; nach Deaktivierung bleibt der Stand gleich. Jede Stufe wartet sechs Frames.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_controls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_controls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\mapper_banks.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:326
Alias the MMC3 IRQ latch/reload operation; other mapper profiles are rejected.
__memcpy
Kopiert die angegebene Anzahl Bytes von src nach dst. Der 16-Bit-Zähler erlaubt auch Übertragungen mit mehr als 255 Bytes, etwa eine vollständige Tabelle im RAM. Gelesen und geschrieben wird in aufsteigender Adressfolge.
Syntax
void __memcpy(u8* dst, const u8* src, u16 len);Parameter
dstZeiger auf das erste beschreibbare Zielbyte. Stellen Sie genügend zusammenhängenden Speicher für die gesamte Operation bereit; ein Puffer wird nicht automatisch angelegt.
srcZeiger auf das erste lesbare Quellbyte. Der gesamte Bereich muss während der Kopie zugänglich und unverändert bleiben.
lenByteanzahl als
u16(0–65535), keine Elementanzahl. Der Höchstwert des Typs bedeutet nicht, dass auf der Zielmaschine ein gültiger Puffer dieser Größe verfügbar ist. Der gesamte Bereich muss in die gewählte Speicherabbildung passen.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Bei Länge null werden keine Bytes übertragen. Seiteneffekte in den Argumentausdrücken finden trotzdem statt: Das Beispiel übergibt zwei Funktionen, die Zeiger liefern, und bestätigt beide Aufrufe trotz Kopierlänge null.
Verwenden Sie Quell- und Zielbereiche, die sich nicht überlappen. Diese Routinen kopieren vorwärts und bieten nicht das überlappungssichere Verhalten von memmove. Schreibzugriffe auf noch ungelesene Quellbytes können die spätere Kopie verfälschen.
Die Operation wird vor der Rückkehr abgeschlossen. Sie schaltet keine ROM/RAM-Bänke um und begrenzt den Bereich nicht automatisch auf Puffergrenzen. Halten Sie die Speicherabbildung stabil, verwenden Sie gültige Bereiche und stimmen Sie Zugriffe mit Interrupt-Routinen ab, die dieselben Puffer verwenden.
Auf dem FC ist dst eine CPU-Adresse, keine PPU-Adresse. Eine Kopie ins CPU-RAM aktualisiert weder CHR noch Nametables oder Paletten; verwenden Sie dafür die PPU/VRAM-APIs. Generierte Hilfsroutinen teilen sich Argument- und Arbeitsspeicher. Rufen Sie sie während einer laufenden Operation nicht erneut aus einer NMI/IRQ-Routine auf.
Anwendungsbeispiel
memory_reset();
__memcpy(memory_destination+1,memory_source+1,257);
memory_copy_word=memory_check(257,0); // 257 copied bytes; guards unchanged.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel kopiert mit __memcpy ab Offset 1 ein bekanntes Muster von 257 Bytes. memory_check vergleicht die vollständigen Quell- und Zielarrays; die Zeile COPY WORD muss 257 anzeigen. Das erste Byte bleibt als Schutzbereich frei. Auch die Bytes nach dem Nutzdatenbereich müssen unverändert bleiben.
Das vollständige Programm initialisiert vor jeder Operation zwei 264-Byte-Puffer und prüft danach jedes Quell- und Zielbyte sowie die unveränderten Schutzbereiche. Erwartet werden COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 und FAILED 000. WORD/BYTE bezeichnen die Zählerbreite, nicht die Größe der kopierten Elemente. Die Zahlen stammen aus den Prüfungen des Beispiels; diese void-Funktionen liefern keine Rückgabewerte.
Die Aufnahmen zeigen schwarze Schrift auf Weiß beim DMG, blaue Schrift auf Weiß beim CGB und blaue Schrift auf Schwarz beim FC. Das Beispiel prüft RAM-Operationen; die Schrift stellt die Ergebnisse dar und demonstriert keine Zeichenprimitive. Weitere Emulatorprüfungen decken je Plattform 88 Speicherfälle ab: konstante und zur Laufzeit bestimmte Längen von 0, 1, 16, 17, 32, 33, 64, 65 und 255 Bytes sowie 256 und 257 bei 16-Bit-Zählern. Auf echter Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\memory_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:19
memory / bit / math Copy len bytes forward between readable/writable CPU ranges; overlapping copies are not memmove. Zero length performs no memory transfer; argument side effects are evaluated before expansion.
kitaqfc/kitaqfc/CodeGenerator.cs:4607
case "__memcpy":
if (TryEmitMemcpyOrMemsetInline(funcName, args, ctx, isSet: false, lenIndex: 2, lenSize: 2)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,2}, "__memcpy");
bool TryEmitMemcpyOrMemsetInline(string funcName, Expr[] args, FunctionContext ctx, bool isSet, int lenIndex, int lenSize)
{
args = args ?? Array.Empty<Expr>();
int expected = isSet ? 3 : 3;
if (args.Length != expected) return false;
if (!TryEvaluateConstant(args[lenIndex], out int len)) return false;
if (len < 0 || len > 32) return false;
int valueConst = 0;
if (isSet && !TryEvaluateConstant(args[1], out valueConst)) return false;
// A zero-length expansion emits no address or fill evaluation in this helper.
if (len == 0) return true;
if (isSet)
{
int[] ptr = AcquireTemps(2);
try
{
EmitLoadValue(args[0], ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", Imm(valueConst));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
return true;
}
EmitFixedMemcpy(args[0], args[1], len, ctx);
return true;
}
void EmitHelperMemoryAndBit()
{
// Promote the byte count to a word by clearing its high byte before entering memcpy.
EmitHelperStart("__memcpy_small");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 5));
EmitAsm("JMP", Abs("__memcpy"));
// Advance both argument pointers and decrement the word count in place; overlapping copies are not reversed.
EmitHelperStart("__memcpy");
string mloop = NewGeneratedLabel("memcpy_loop");
string mdone = NewGeneratedLabel("memcpy_done");
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BEQ", Rel(mdone));
_assembly.Add(Expr.Make(Tag.Label, mloop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase + 2));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INC", Mem(CallArgBase + 2));
string ms1 = NewGeneratedLabel("memcpy_src_page_ok");
EmitAsm("BNE", Rel(ms1));
EmitAsm("INC", Mem(CallArgBase + 3));
_assembly.Add(Expr.Make(Tag.Label, ms1));
EmitAsm("INC", Mem(CallArgBase));
string md1 = NewGeneratedLabel("memcpy_dst_page_ok");
EmitAsm("BNE", Rel(md1));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, md1));
EmitAsm("LDA", Mem(CallArgBase + 4));
string mlononzero = NewGeneratedLabel("memcpy_lenlo_nonzero");
EmitAsm("BNE", Rel(mlononzero));
EmitAsm("DEC", Mem(CallArgBase + 5));
_assembly.Add(Expr.Make(Tag.Label, mlononzero));
EmitAsm("DEC", Mem(CallArgBase + 4));
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BNE", Rel(mloop));
_assembly.Add(Expr.Make(Tag.Label, mdone));
EmitAsm("RTS");
// Clear the high count byte for the small-fill wrapper.
EmitHelperStart("__memset_small");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 4));
EmitAsm("JMP", Abs("__memset"));
// Advance the destination pointer while consuming the word count in the shared argument area.
EmitHelperStart("__memset");
string sloop = NewGeneratedLabel("memset_loop");
string sdone = NewGeneratedLabel("memset_done");
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ORA", Mem(CallArgBase + 4));
EmitAsm("BEQ", Rel(sdone));
_assembly.Add(Expr.Make(Tag.Label, sloop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INC", Mem(CallArgBase));
string sd1 = NewGeneratedLabel("memset_dst_page_ok");
EmitAsm("BNE", Rel(sd1));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, sd1));
EmitAsm("LDA", Mem(CallArgBase + 3));
string slononzero = NewGeneratedLabel("memset_lenlo_nonzero");
EmitAsm("BNE", Rel(slononzero));
EmitAsm("DEC", Mem(CallArgBase + 4));
_assembly.Add(Expr.Make(Tag.Label, slononzero));
EmitAsm("DEC", Mem(CallArgBase + 3));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ORA", Mem(CallArgBase + 4));
EmitAsm("BNE", Rel(sloop));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("RTS");
}
void EmitFixedMemcpy(Expr dstExpr, Expr srcExpr, int len, FunctionContext ctx)
{
int[] ptr = AcquireTemps(4);
try
{
EmitLoadValue(dstExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitLoadValue(srcExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[2]));
EmitAsm("STX", Mem(ptr[3]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", IndY(ptr[2]));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
}
// kitaqfc/kitaqfc/Lowerer.cs:2128
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
Kopiert mit einem 8-Bit-Zähler einen Block von höchstens 255 Bytes. Diese Form eignet sich, wenn die Länge in ein Byte passt. _small bezeichnet die Breite des Zählers und garantiert weder eine bestimmte Laufzeit noch eine bestimmte Codegröße.
Syntax
void __memcpy_small(u8* dst, const u8* src, u8 len);Parameter
dstZeiger auf das erste beschreibbare Zielbyte. Stellen Sie genügend zusammenhängenden Speicher für die gesamte Operation bereit; ein Puffer wird nicht automatisch angelegt.
srcZeiger auf das erste lesbare Quellbyte. Der gesamte Bereich muss während der Kopie zugänglich und unverändert bleiben.
lenByteanzahl als
u8: 0–255. Übergeben Sie nicht 256, um einen vollständigen 256-Byte-Block anzugeben; diese Länge passt nicht in den Parameter. Ab 256 Bytes verwenden Sie die entsprechende Funktion ohne_small.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Bei Länge null werden keine Bytes übertragen. Seiteneffekte in den Argumentausdrücken finden trotzdem statt: Das Beispiel übergibt zwei Funktionen, die Zeiger liefern, und bestätigt beide Aufrufe trotz Kopierlänge null.
Verwenden Sie Quell- und Zielbereiche, die sich nicht überlappen. Diese Routinen kopieren vorwärts und bieten nicht das überlappungssichere Verhalten von memmove. Schreibzugriffe auf noch ungelesene Quellbytes können die spätere Kopie verfälschen.
Die Operation wird vor der Rückkehr abgeschlossen. Sie schaltet keine ROM/RAM-Bänke um und begrenzt den Bereich nicht automatisch auf Puffergrenzen. Halten Sie die Speicherabbildung stabil, verwenden Sie gültige Bereiche und stimmen Sie Zugriffe mit Interrupt-Routinen ab, die dieselben Puffer verwenden.
Auf dem FC ist dst eine CPU-Adresse, keine PPU-Adresse. Eine Kopie ins CPU-RAM aktualisiert weder CHR noch Nametables oder Paletten; verwenden Sie dafür die PPU/VRAM-APIs. Generierte Hilfsroutinen teilen sich Argument- und Arbeitsspeicher. Rufen Sie sie während einer laufenden Operation nicht erneut aus einer NMI/IRQ-Routine auf.
Anwendungsbeispiel
memory_reset();
__memcpy_small(memory_destination+1,memory_source+1,255);
memory_copy_byte=memory_check(255,0); // Maximum representable byte count.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel kopiert mit __memcpy_small ab Offset 1 ein bekanntes Muster von 255 Bytes. memory_check vergleicht die vollständigen Quell- und Zielarrays; die Zeile COPY BYTE muss 255 anzeigen. Das erste Byte bleibt als Schutzbereich frei. Auch die Bytes nach dem Nutzdatenbereich müssen unverändert bleiben.
Das vollständige Programm initialisiert vor jeder Operation zwei 264-Byte-Puffer und prüft danach jedes Quell- und Zielbyte sowie die unveränderten Schutzbereiche. Erwartet werden COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 und FAILED 000. WORD/BYTE bezeichnen die Zählerbreite, nicht die Größe der kopierten Elemente. Die Zahlen stammen aus den Prüfungen des Beispiels; diese void-Funktionen liefern keine Rückgabewerte.
Die Aufnahmen zeigen schwarze Schrift auf Weiß beim DMG, blaue Schrift auf Weiß beim CGB und blaue Schrift auf Schwarz beim FC. Das Beispiel prüft RAM-Operationen; die Schrift stellt die Ergebnisse dar und demonstriert keine Zeichenprimitive. Weitere Emulatorprüfungen decken je Plattform 88 Speicherfälle ab: konstante und zur Laufzeit bestimmte Längen von 0, 1, 16, 17, 32, 33, 64, 65 und 255 Bytes sowie 256 und 257 bei 16-Bit-Zählern. Auf echter Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\memory_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:23
Forward copy with an 8-bit length, zero through 255; the dynamic helper promotes it to a word count.
kitaqfc/kitaqfc/CodeGenerator.cs:4610
case "__memcpy_small":
if (TryEmitMemcpyOrMemsetInline(funcName, args, ctx, isSet: false, lenIndex: 2, lenSize: 1)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,1}, "__memcpy_small");
bool TryEmitMemcpyOrMemsetInline(string funcName, Expr[] args, FunctionContext ctx, bool isSet, int lenIndex, int lenSize)
{
args = args ?? Array.Empty<Expr>();
int expected = isSet ? 3 : 3;
if (args.Length != expected) return false;
if (!TryEvaluateConstant(args[lenIndex], out int len)) return false;
if (len < 0 || len > 32) return false;
int valueConst = 0;
if (isSet && !TryEvaluateConstant(args[1], out valueConst)) return false;
// A zero-length expansion emits no address or fill evaluation in this helper.
if (len == 0) return true;
if (isSet)
{
int[] ptr = AcquireTemps(2);
try
{
EmitLoadValue(args[0], ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", Imm(valueConst));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
return true;
}
EmitFixedMemcpy(args[0], args[1], len, ctx);
return true;
}
void EmitHelperMemoryAndBit()
{
// Promote the byte count to a word by clearing its high byte before entering memcpy.
EmitHelperStart("__memcpy_small");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 5));
EmitAsm("JMP", Abs("__memcpy"));
// Advance both argument pointers and decrement the word count in place; overlapping copies are not reversed.
EmitHelperStart("__memcpy");
string mloop = NewGeneratedLabel("memcpy_loop");
string mdone = NewGeneratedLabel("memcpy_done");
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BEQ", Rel(mdone));
_assembly.Add(Expr.Make(Tag.Label, mloop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase + 2));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INC", Mem(CallArgBase + 2));
string ms1 = NewGeneratedLabel("memcpy_src_page_ok");
EmitAsm("BNE", Rel(ms1));
EmitAsm("INC", Mem(CallArgBase + 3));
_assembly.Add(Expr.Make(Tag.Label, ms1));
EmitAsm("INC", Mem(CallArgBase));
string md1 = NewGeneratedLabel("memcpy_dst_page_ok");
EmitAsm("BNE", Rel(md1));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, md1));
EmitAsm("LDA", Mem(CallArgBase + 4));
string mlononzero = NewGeneratedLabel("memcpy_lenlo_nonzero");
EmitAsm("BNE", Rel(mlononzero));
EmitAsm("DEC", Mem(CallArgBase + 5));
_assembly.Add(Expr.Make(Tag.Label, mlononzero));
EmitAsm("DEC", Mem(CallArgBase + 4));
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BNE", Rel(mloop));
_assembly.Add(Expr.Make(Tag.Label, mdone));
EmitAsm("RTS");
// Clear the high count byte for the small-fill wrapper.
EmitHelperStart("__memset_small");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 4));
EmitAsm("JMP", Abs("__memset"));
// Advance the destination pointer while consuming the word count in the shared argument area.
EmitHelperStart("__memset");
string sloop = NewGeneratedLabel("memset_loop");
string sdone = NewGeneratedLabel("memset_done");
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ORA", Mem(CallArgBase + 4));
EmitAsm("BEQ", Rel(sdone));
_assembly.Add(Expr.Make(Tag.Label, sloop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INC", Mem(CallArgBase));
string sd1 = NewGeneratedLabel("memset_dst_page_ok");
EmitAsm("BNE", Rel(sd1));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, sd1));
EmitAsm("LDA", Mem(CallArgBase + 3));
string slononzero = NewGeneratedLabel("memset_lenlo_nonzero");
EmitAsm("BNE", Rel(slononzero));
EmitAsm("DEC", Mem(CallArgBase + 4));
_assembly.Add(Expr.Make(Tag.Label, slononzero));
EmitAsm("DEC", Mem(CallArgBase + 3));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ORA", Mem(CallArgBase + 4));
EmitAsm("BNE", Rel(sloop));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("RTS");
}
void EmitFixedMemcpy(Expr dstExpr, Expr srcExpr, int len, FunctionContext ctx)
{
int[] ptr = AcquireTemps(4);
try
{
EmitLoadValue(dstExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitLoadValue(srcExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[2]));
EmitAsm("STX", Mem(ptr[3]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", IndY(ptr[2]));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
}
// kitaqfc/kitaqfc/Lowerer.cs:2128
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
Füllt den Zielbereich mit demselben Bytewert. Der 16-Bit-Zähler eignet sich zum Leeren größerer Puffer oder zum Initialisieren einer Tabelle. Mit 0x5A wird beispielsweise 5A 5A 5A… geschrieben; eine mehrbyteige Ganzzahl wird dabei nicht wiederholt.
Syntax
void __memset(u8* dst, u8 value, u16 len);Parameter
dstZeiger auf das erste beschreibbare Zielbyte. Stellen Sie genügend zusammenhängenden Speicher für die gesamte Operation bereit; ein Puffer wird nicht automatisch angelegt.
valueBytewert von 0 bis 255, der an jede Zieladresse geschrieben wird. Mit 0 wird der Bereich gelöscht. Soll ein
u16-Array einen numerischen Wert ungleich null erhalten, weisen Sie diesen jedem Element einzeln zu.lenByteanzahl als
u16(0–65535), keine Elementanzahl. Der Höchstwert des Typs bedeutet nicht, dass auf der Zielmaschine ein gültiger Puffer dieser Größe verfügbar ist. Der gesamte Bereich muss in die gewählte Speicherabbildung passen.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Bei Länge null werden keine Bytes übertragen. Seiteneffekte in den Argumentausdrücken finden trotzdem statt: Das Beispiel übergibt zwei Funktionen, die Zeiger liefern, und bestätigt beide Aufrufe trotz Kopierlänge null.
Die Operation wird vor der Rückkehr abgeschlossen. Sie schaltet keine ROM/RAM-Bänke um und begrenzt den Bereich nicht automatisch auf Puffergrenzen. Halten Sie die Speicherabbildung stabil, verwenden Sie gültige Bereiche und stimmen Sie Zugriffe mit Interrupt-Routinen ab, die dieselben Puffer verwenden.
Auf dem FC ist dst eine CPU-Adresse, keine PPU-Adresse. Eine Kopie ins CPU-RAM aktualisiert weder CHR noch Nametables oder Paletten; verwenden Sie dafür die PPU/VRAM-APIs. Generierte Hilfsroutinen teilen sich Argument- und Arbeitsspeicher. Rufen Sie sie während einer laufenden Operation nicht erneut aus einer NMI/IRQ-Routine auf.
Anwendungsbeispiel
memory_reset();
__memset(memory_destination+1,0x5A,257);
memory_fill_word=memory_check(257,1); // Repeat one byte, not a 16-bit word.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel füllt mit __memset ab Offset 1 genau 257 Bytes mit 0x5A. memory_check verlangt für jedes Zielbyte 0x5A, für die Schutzbytes weiterhin 0xCC und einen unveränderten separaten Quellpuffer. Die Zeile FILL WORD muss 257 anzeigen.
Das vollständige Programm initialisiert vor jeder Operation zwei 264-Byte-Puffer und prüft danach jedes Quell- und Zielbyte sowie die unveränderten Schutzbereiche. Erwartet werden COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 und FAILED 000. WORD/BYTE bezeichnen die Zählerbreite, nicht die Größe der kopierten Elemente. Die Zahlen stammen aus den Prüfungen des Beispiels; diese void-Funktionen liefern keine Rückgabewerte.
Die Aufnahmen zeigen schwarze Schrift auf Weiß beim DMG, blaue Schrift auf Weiß beim CGB und blaue Schrift auf Schwarz beim FC. Das Beispiel prüft RAM-Operationen; die Schrift stellt die Ergebnisse dar und demonstriert keine Zeichenprimitive. Weitere Emulatorprüfungen decken je Plattform 88 Speicherfälle ab: konstante und zur Laufzeit bestimmte Längen von 0, 1, 16, 17, 32, 33, 64, 65 und 255 Bytes sowie 256 und 257 bei 16-Bit-Zählern. Auf echter Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\memory_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:21
Fill len CPU bytes with value. Zero length still evaluates argument side effects.
kitaqfc/kitaqfc/CodeGenerator.cs:4613
case "__memset":
if (TryEmitMemcpyOrMemsetInline(funcName, args, ctx, isSet: true, lenIndex: 2, lenSize: 2)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,1,2}, "__memset");
bool TryEmitMemcpyOrMemsetInline(string funcName, Expr[] args, FunctionContext ctx, bool isSet, int lenIndex, int lenSize)
{
args = args ?? Array.Empty<Expr>();
int expected = isSet ? 3 : 3;
if (args.Length != expected) return false;
if (!TryEvaluateConstant(args[lenIndex], out int len)) return false;
if (len < 0 || len > 32) return false;
int valueConst = 0;
if (isSet && !TryEvaluateConstant(args[1], out valueConst)) return false;
// A zero-length expansion emits no address or fill evaluation in this helper.
if (len == 0) return true;
if (isSet)
{
int[] ptr = AcquireTemps(2);
try
{
EmitLoadValue(args[0], ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", Imm(valueConst));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
return true;
}
EmitFixedMemcpy(args[0], args[1], len, ctx);
return true;
}
void EmitHelperMemoryAndBit()
{
// Promote the byte count to a word by clearing its high byte before entering memcpy.
EmitHelperStart("__memcpy_small");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 5));
EmitAsm("JMP", Abs("__memcpy"));
// Advance both argument pointers and decrement the word count in place; overlapping copies are not reversed.
EmitHelperStart("__memcpy");
string mloop = NewGeneratedLabel("memcpy_loop");
string mdone = NewGeneratedLabel("memcpy_done");
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BEQ", Rel(mdone));
_assembly.Add(Expr.Make(Tag.Label, mloop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase + 2));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INC", Mem(CallArgBase + 2));
string ms1 = NewGeneratedLabel("memcpy_src_page_ok");
EmitAsm("BNE", Rel(ms1));
EmitAsm("INC", Mem(CallArgBase + 3));
_assembly.Add(Expr.Make(Tag.Label, ms1));
EmitAsm("INC", Mem(CallArgBase));
string md1 = NewGeneratedLabel("memcpy_dst_page_ok");
EmitAsm("BNE", Rel(md1));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, md1));
EmitAsm("LDA", Mem(CallArgBase + 4));
string mlononzero = NewGeneratedLabel("memcpy_lenlo_nonzero");
EmitAsm("BNE", Rel(mlononzero));
EmitAsm("DEC", Mem(CallArgBase + 5));
_assembly.Add(Expr.Make(Tag.Label, mlononzero));
EmitAsm("DEC", Mem(CallArgBase + 4));
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BNE", Rel(mloop));
_assembly.Add(Expr.Make(Tag.Label, mdone));
EmitAsm("RTS");
// Clear the high count byte for the small-fill wrapper.
EmitHelperStart("__memset_small");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 4));
EmitAsm("JMP", Abs("__memset"));
// Advance the destination pointer while consuming the word count in the shared argument area.
EmitHelperStart("__memset");
string sloop = NewGeneratedLabel("memset_loop");
string sdone = NewGeneratedLabel("memset_done");
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ORA", Mem(CallArgBase + 4));
EmitAsm("BEQ", Rel(sdone));
_assembly.Add(Expr.Make(Tag.Label, sloop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INC", Mem(CallArgBase));
string sd1 = NewGeneratedLabel("memset_dst_page_ok");
EmitAsm("BNE", Rel(sd1));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, sd1));
EmitAsm("LDA", Mem(CallArgBase + 3));
string slononzero = NewGeneratedLabel("memset_lenlo_nonzero");
EmitAsm("BNE", Rel(slononzero));
EmitAsm("DEC", Mem(CallArgBase + 4));
_assembly.Add(Expr.Make(Tag.Label, slononzero));
EmitAsm("DEC", Mem(CallArgBase + 3));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ORA", Mem(CallArgBase + 4));
EmitAsm("BNE", Rel(sloop));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("RTS");
}
void EmitFixedMemcpy(Expr dstExpr, Expr srcExpr, int len, FunctionContext ctx)
{
int[] ptr = AcquireTemps(4);
try
{
EmitLoadValue(dstExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitLoadValue(srcExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[2]));
EmitAsm("STX", Mem(ptr[3]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", IndY(ptr[2]));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
}
// kitaqfc/kitaqfc/Lowerer.cs:2128
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
Füllt mit einem 8-Bit-Zähler bis zu 255 aufeinanderfolgende Bytes mit demselben Wert. Das eignet sich zum Initialisieren eines kleinen Arbeitspuffers. Für mehr als 255 Bytes verwenden Sie __memset.
Syntax
void __memset_small(u8* dst, u8 value, u8 len);Parameter
dstZeiger auf das erste beschreibbare Zielbyte. Stellen Sie genügend zusammenhängenden Speicher für die gesamte Operation bereit; ein Puffer wird nicht automatisch angelegt.
valueBytewert von 0 bis 255, der an jede Zieladresse geschrieben wird. Mit 0 wird der Bereich gelöscht. Soll ein
u16-Array einen numerischen Wert ungleich null erhalten, weisen Sie diesen jedem Element einzeln zu.lenByteanzahl als
u8: 0–255. Übergeben Sie nicht 256, um einen vollständigen 256-Byte-Block anzugeben; diese Länge passt nicht in den Parameter. Ab 256 Bytes verwenden Sie die entsprechende Funktion ohne_small.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Bei Länge null werden keine Bytes übertragen. Seiteneffekte in den Argumentausdrücken finden trotzdem statt: Das Beispiel übergibt zwei Funktionen, die Zeiger liefern, und bestätigt beide Aufrufe trotz Kopierlänge null.
Die Operation wird vor der Rückkehr abgeschlossen. Sie schaltet keine ROM/RAM-Bänke um und begrenzt den Bereich nicht automatisch auf Puffergrenzen. Halten Sie die Speicherabbildung stabil, verwenden Sie gültige Bereiche und stimmen Sie Zugriffe mit Interrupt-Routinen ab, die dieselben Puffer verwenden.
Auf dem FC ist dst eine CPU-Adresse, keine PPU-Adresse. Eine Kopie ins CPU-RAM aktualisiert weder CHR noch Nametables oder Paletten; verwenden Sie dafür die PPU/VRAM-APIs. Generierte Hilfsroutinen teilen sich Argument- und Arbeitsspeicher. Rufen Sie sie während einer laufenden Operation nicht erneut aus einer NMI/IRQ-Routine auf.
Anwendungsbeispiel
memory_reset();
__memset_small(memory_destination+1,0x5A,255);
memory_fill_byte=memory_check(255,1);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel füllt mit __memset_small ab Offset 1 genau 255 Bytes mit 0x5A. memory_check verlangt für jedes Zielbyte 0x5A, für die Schutzbytes weiterhin 0xCC und einen unveränderten separaten Quellpuffer. Die Zeile FILL BYTE muss 255 anzeigen.
Das vollständige Programm initialisiert vor jeder Operation zwei 264-Byte-Puffer und prüft danach jedes Quell- und Zielbyte sowie die unveränderten Schutzbereiche. Erwartet werden COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 und FAILED 000. WORD/BYTE bezeichnen die Zählerbreite, nicht die Größe der kopierten Elemente. Die Zahlen stammen aus den Prüfungen des Beispiels; diese void-Funktionen liefern keine Rückgabewerte.
Die Aufnahmen zeigen schwarze Schrift auf Weiß beim DMG, blaue Schrift auf Weiß beim CGB und blaue Schrift auf Schwarz beim FC. Das Beispiel prüft RAM-Operationen; die Schrift stellt die Ergebnisse dar und demonstriert keine Zeichenprimitive. Weitere Emulatorprüfungen decken je Plattform 88 Speicherfälle ab: konstante und zur Laufzeit bestimmte Längen von 0, 1, 16, 17, 32, 33, 64, 65 und 255 Bytes sowie 256 und 257 bei 16-Bit-Zählern. Auf echter Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\memory_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\memory_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:25
Fill zero through 255 CPU bytes; this is ordinary RAM access, not a PPU transfer.
kitaqfc/kitaqfc/CodeGenerator.cs:4616
case "__memset_small":
if (TryEmitMemcpyOrMemsetInline(funcName, args, ctx, isSet: true, lenIndex: 2, lenSize: 1)) return true;
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,1,1}, "__memset_small");
// These fixed-size copies bypass the variable-length helper argument area.
bool TryEmitMemcpyOrMemsetInline(string funcName, Expr[] args, FunctionContext ctx, bool isSet, int lenIndex, int lenSize)
{
args = args ?? Array.Empty<Expr>();
int expected = isSet ? 3 : 3;
if (args.Length != expected) return false;
if (!TryEvaluateConstant(args[lenIndex], out int len)) return false;
if (len < 0 || len > 32) return false;
int valueConst = 0;
if (isSet && !TryEvaluateConstant(args[1], out valueConst)) return false;
// A zero-length expansion emits no address or fill evaluation in this helper.
if (len == 0) return true;
if (isSet)
{
int[] ptr = AcquireTemps(2);
try
{
EmitLoadValue(args[0], ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", Imm(valueConst));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
return true;
}
EmitFixedMemcpy(args[0], args[1], len, ctx);
return true;
}
void EmitHelperMemoryAndBit()
{
// Promote the byte count to a word by clearing its high byte before entering memcpy.
EmitHelperStart("__memcpy_small");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 5));
EmitAsm("JMP", Abs("__memcpy"));
// Advance both argument pointers and decrement the word count in place; overlapping copies are not reversed.
EmitHelperStart("__memcpy");
string mloop = NewGeneratedLabel("memcpy_loop");
string mdone = NewGeneratedLabel("memcpy_done");
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BEQ", Rel(mdone));
_assembly.Add(Expr.Make(Tag.Label, mloop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase + 2));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INC", Mem(CallArgBase + 2));
string ms1 = NewGeneratedLabel("memcpy_src_page_ok");
EmitAsm("BNE", Rel(ms1));
EmitAsm("INC", Mem(CallArgBase + 3));
_assembly.Add(Expr.Make(Tag.Label, ms1));
EmitAsm("INC", Mem(CallArgBase));
string md1 = NewGeneratedLabel("memcpy_dst_page_ok");
EmitAsm("BNE", Rel(md1));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, md1));
EmitAsm("LDA", Mem(CallArgBase + 4));
string mlononzero = NewGeneratedLabel("memcpy_lenlo_nonzero");
EmitAsm("BNE", Rel(mlononzero));
EmitAsm("DEC", Mem(CallArgBase + 5));
_assembly.Add(Expr.Make(Tag.Label, mlononzero));
EmitAsm("DEC", Mem(CallArgBase + 4));
EmitAsm("LDA", Mem(CallArgBase + 4));
EmitAsm("ORA", Mem(CallArgBase + 5));
EmitAsm("BNE", Rel(mloop));
_assembly.Add(Expr.Make(Tag.Label, mdone));
EmitAsm("RTS");
// Clear the high count byte for the small-fill wrapper.
EmitHelperStart("__memset_small");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 4));
EmitAsm("JMP", Abs("__memset"));
// Advance the destination pointer while consuming the word count in the shared argument area.
EmitHelperStart("__memset");
string sloop = NewGeneratedLabel("memset_loop");
string sdone = NewGeneratedLabel("memset_done");
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ORA", Mem(CallArgBase + 4));
EmitAsm("BEQ", Rel(sdone));
_assembly.Add(Expr.Make(Tag.Label, sloop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", IndY(CallArgBase));
EmitAsm("INC", Mem(CallArgBase));
string sd1 = NewGeneratedLabel("memset_dst_page_ok");
EmitAsm("BNE", Rel(sd1));
EmitAsm("INC", Mem(CallArgBase + 1));
_assembly.Add(Expr.Make(Tag.Label, sd1));
EmitAsm("LDA", Mem(CallArgBase + 3));
string slononzero = NewGeneratedLabel("memset_lenlo_nonzero");
EmitAsm("BNE", Rel(slononzero));
EmitAsm("DEC", Mem(CallArgBase + 4));
_assembly.Add(Expr.Make(Tag.Label, slononzero));
EmitAsm("DEC", Mem(CallArgBase + 3));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("ORA", Mem(CallArgBase + 4));
EmitAsm("BNE", Rel(sloop));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("RTS");
}
void EmitFixedMemcpy(Expr dstExpr, Expr srcExpr, int len, FunctionContext ctx)
{
int[] ptr = AcquireTemps(4);
try
{
EmitLoadValue(dstExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[0]));
EmitAsm("STX", Mem(ptr[1]));
EmitLoadValue(srcExpr, ctx, 2);
EmitAsm("STA", Mem(ptr[2]));
EmitAsm("STX", Mem(ptr[3]));
for (int i = 0; i < len; i++)
{
EmitAsm("LDY", Imm(i));
EmitAsm("LDA", IndY(ptr[2]));
EmitAsm("STA", IndY(ptr[0]));
}
}
finally { ReleaseTemps(ptr); }
}
// kitaqfc/kitaqfc/Lowerer.cs:2128
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);
}__metasprite_draw
Entpackt eine mit einer Endmarkierung versehene Folge von Sprite-Teilen in aufeinanderfolgende Einträge des Schattenpuffers der Seite 02. Jeder Teil addiert seine X/Y-Offsets von je einem Byte zur Basisposition und enthält eine eigene Kachel samt Attributen. Zurückgegeben wird der nächste OAM-Index.
Syntax
u8 __metasprite_draw(u8 oam_index, u8 base_x, u8 base_y, const u8* metasprite);Parameter
oam_indexSprite-Index von 0 bis 63, kein Byte-Offset. Intern wird er mit vier multipliziert und als 8-Bit-Position gespeichert. Werte außerhalb des Bereichs werden modulo 64 umgebrochen und können einen anderen Eintrag überschreiben. Eine Belegungsprüfung findet nicht statt.
base_xHorizontale Position in Pixeln als vorzeichenloses Byte; keine Tile-Koordinate.
base_yY-Byte des OAM. Für eine Oberkante unterhalb der Zeile null wird ihre Position minus eins übergeben; 239..255 liegen außerhalb der 240 sichtbaren Zeilen.
metaspriteZeiger auf lesbare Datensätze aus vier Byte:
dx, dy, tile, attr. Ein0xFFan der nächsten dx-Position beendet die Folge; weitere Bytes sind nicht nötig. Offsets werden modulo 256 addiert:0xFEsteht für −2 und dy=0xFFdarf −1 darstellen. dx=0xFFist hingegen die Endmarkierung und kann nicht für −1 verwendet werden. Der Zeiger muss im eingeblendeten Speicher gültig sein.
Rückgabewert
Nächster OAM-Index: (oam_index + geschriebene_Teile) modulo 64. Das ist eine Schreibposition, nicht die Anzahl gezeichneter Teile. Bei einer leeren Folge wird oam_index modulo 64 zurückgegeben.
Bedingungen und Grenzen
Diese Operationen ändern den CPU-RAM bei 0x0200–0x02FF, nicht sofort das Hardware-OAM. Übertragen Sie die fertige Seite mit __oam_dma. Belegungsflags, Koordinatenfelder und Zähler der Sprite-Bibliothek werden nicht mitgeführt. Interrupt-Routinen dürfen eine noch unvollständige Seite weder ändern noch übertragen.
Anzahl der Teile, Bildausschnitt und Kapazität werden nicht geprüft. Fügen Sie eine Endmarkierung ein und verwenden Sie höchstens 64−oam_index Teile. Koordinaten laufen modulo 256 über; nach Eintrag 63 wird bei Eintrag 0 weitergeschrieben. Eine leere Folge ändert nichts. Das Format unterscheidet sich vom MetaSpritePart-Array mit expliziter Anzahl, das metasprite_draw in der Sprite-Bibliothek verwendet.
Anwendungsbeispiel
fc_oam_next=__metasprite_draw(5,136,95,fc_oam_pair); // Returns 7 after two entries.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.
TRANSFER zeigt einen Pfeil bei (144,128). Dieses Programm überträgt Seite 02 direkt mit __oam_dma, einschließlich aller durch die einzelnen Sprite-Operationen vorbereiteten Felder.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:203
Expand dx,dy,tile,attr records into page-02 shadow OAM until dx=FF; return the next index. Coordinates and the byte OAM cursor wrap; keep the terminated stream within the available slots.
EmitHelperStart("__metasprite_draw");
// args: oam_index, base_x, base_y, metasprite_ptr16.
// stream: dx, dy, tile, attr ... dx=$FF terminates. Returns next OAM index in A.
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("ASL");
EmitAsm("ASL");
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address)); // dst byte offset
string msLoop = NewGeneratedLabel("metasprite_loop");
string msDone = NewGeneratedLabel("metasprite_done");
_assembly.Add(Expr.Make(Tag.Label, msLoop));
EmitAsm("LDY", Imm(0));
EmitAsm("LDA", IndY(CallArgBase + 3));
EmitAsm("CMP", Imm(0xFF));
EmitAsm("BEQ", Rel(msDone));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address)); // dx
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("INY");
EmitAsm("LDA", IndY(CallArgBase + 3));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase + 2));
EmitAsm("STA", new AsmOperand(OamRamBase + 0, AddressMode.AbsoluteX));
EmitAsm("INY");
EmitAsm("LDA", IndY(CallArgBase + 3));
EmitAsm("STA", new AsmOperand(OamRamBase + 1, AddressMode.AbsoluteX));
EmitAsm("INY");
EmitAsm("LDA", IndY(CallArgBase + 3));
EmitAsm("STA", new AsmOperand(OamRamBase + 2, AddressMode.AbsoluteX));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", new AsmOperand(OamRamBase + 3, AddressMode.AbsoluteX));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("CLC");
EmitAsm("ADC", Imm(4));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("CLC");
EmitAsm("ADC", Imm(4));
EmitAsm("STA", Mem(CallArgBase + 3));
// Propagate stream-pointer page carry after consuming the complete four-byte record.
string msPtrOk = NewGeneratedLabel("metasprite_ptr_ok");
EmitAsm("BCC", Rel(msPtrOk));
EmitAsm("INC", Mem(CallArgBase + 4));
_assembly.Add(Expr.Make(Tag.Label, msPtrOk));
EmitAsm("JMP", Abs(msLoop));
_assembly.Add(Expr.Make(Tag.Label, msDone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LSR");
EmitAsm("LSR");
EmitAsm("RTS");__mic_read2p
Liest das digitale Mikrofonsignal des zweiten Famicom-Controllers aus Bit 2 von $4016. Die Routine liest das Register einmal, maskiert mit 0x04 und normalisiert ohne Invertierung auf 0 oder 1. Sie übernimmt keine Controllerzustände, zeichnet kein Audio auf, misst keine Lautstärke und erkennt keine Sprache.
Syntax
u8 __mic_read2p(void);Rückgabewert
Genau 1, wenn $4016 & 0x04 ungleich null ist, andernfalls 0. Dies ist der gelesene Digitalpegel, keine Tastenmaske, Amplitude, Frequenz oder Anzahl gesprochener Wörter.
Bedingungen und Grenzen
Das Mikrofon gehört zu Controller 2, die Routine greift jedoch auf $4016 statt $4017 zu. Ein Registerzugriff kann bei niedrigem Strobe auch den seriellen Controllerstrom weiterschalten. Fügen Sie daher keine Mikrofonzugriffe in die Achtbit-Leseschleife eines anderen Controllers ein. Ein gespeicherter Eingabeverlauf wird nicht aktualisiert.
Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.
Anwendungsbeispiel
mic_direct=__mic_read2p(); // A separate sample of the same digital signal.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Dieses Programm vergleicht normale D0-Eingaben mit D1 und dem digitalen Mikrofonpegel. Halten Sie A auf Controller 1 und B auf Controller 2 bis zur Erfassung gedrückt. NORMAL P1 und NORMAL P2 zeigen 001 und 002. Im beigefügten KUROSAKI-Lauf zeigen D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL und MIC DIRECT jeweils 000, weil diese Leitungen nicht emuliert werden. Die erfassten Werte bleiben anschließend stehen. Mit geeigneten Peripheriegeräten und implementierten Signalleitungen hängen die Werte von den tatsächlichen Eingaben ab; deuten Sie sie anhand der obigen Bitbelegung, statt diese Nullen zu erwarten.
Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\expansion_input.c -I .\kitaq-docs\samples -I .\kitaqfc\lib -o .\out\expansion_input.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:233
Alias the same normalized microphone input bit as __joypad2p_voice.
__midi_clock
Sendet ein Timing-Clock-Byte F8. Wiederholte Ticks plant der Aufrufer.
Syntax
void __midi_clock(void);Rückgabewert
Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.
Bedingungen und Grenzen
Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.
Anwendungsbeispiel
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}Erwartetes Ergebnis
Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:304
Transmit the single realtime byte F8 synchronously; this does not schedule future ticks.
kitaqfc/kitaqfc/CodeGenerator.cs:4803
case "__midi_clock": if (!Need(0)) return true; EmitAsm("LDA", Imm(0xF8)); EmitAsm("JSR", Abs("__kq_midi_out_a")); return true;
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_continue
Sendet ein Echtzeit-Continue-Byte FB, ohne den internen Musiktreiber zu bedienen.
Syntax
void __midi_continue(void);Rückgabewert
Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.
Bedingungen und Grenzen
Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.
Anwendungsbeispiel
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}Erwartetes Ergebnis
Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:308
Transmit realtime Continue (FB) through the serial adapter path.
kitaqfc/kitaqfc/CodeGenerator.cs:4805
case "__midi_continue": if (!Need(0)) return true; EmitAsm("LDA", Imm(0xFB)); EmitAsm("JSR", Abs("__kq_midi_out_a")); return true;
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_control_change
Sendet Control Change als 0xB0 | (ch & 0x0F), Controllernummer und Wert.
Syntax
void __midi_control_change(u8 ch, u8 cc, u8 value);Parameter
chMIDI-Kanal 0..15; das untere Halbbyte geht ins Statusbyte ein. Null entspricht dem angezeigten MIDI-Kanal 1.
ccControllernummer 0..127; 7 ist Kanallautstärke.
valueControllerwert 0..127; obere Bits werden nicht automatisch gelöscht.
Rückgabewert
Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.
Bedingungen und Grenzen
Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.
Anwendungsbeispiel
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}Erwartetes Ergebnis
Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:300
Send B0|(ch&0F), controller and value; both data arguments must be valid 7-bit values.
kitaqfc/kitaqfc/CodeGenerator.cs:4800
case "__midi_control_change": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1}, funcName);
EmitHelperStart("__midi_control_change");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("AND", Imm(0x0F));
EmitAsm("ORA", Imm(0xB0));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_in_byte
Wartet auf niedrigen Empfangspegel und tastet dann acht Bits in festgelegten Zeitabständen ab.
Syntax
u8 __midi_in_byte(void);Rückgabewert
Liefert das empfangene u8. Keine Zeitbegrenzung, Rahmenprüfung oder Empfangspufferung. Dauerhaft hoher Eingang blockiert unbegrenzt; dauerhaft niedriger ergibt null.
Bedingungen und Grenzen
Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.
Anwendungsbeispiel
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}Erwartetes Ergebnis
Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:294
Block for a low start signal and sample eight bits. No timeout, framing check or receive buffer is provided.
kitaqfc/kitaqfc/CodeGenerator.cs:4796
case "__midi_in_byte": if (!Need(0)) return true; EmitAsm("JSR", Abs("__midi_in_byte")); return true;
// Pack MIDI message fields as bytes; real-time commands below load their fixed status byte directly.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_note_off
Sendet Note-off als drei Bytes: 0x80 | (ch & 0x0F), note, velocity.
Syntax
void __midi_note_off(u8 ch, u8 note, u8 velocity);Parameter
chMIDI-Kanal 0..15; das untere Halbbyte geht ins Statusbyte ein. Null entspricht dem angezeigten MIDI-Kanal 1.
noteMIDI-Note 0..127; 69 ist A4. Es erfolgt keine Siebenbitmaskierung; der Aufrufer hält den Bereich ein.
velocityAnschlagstärke 0..127; keine automatische Siebenbitmaskierung.
Rückgabewert
Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.
Bedingungen und Grenzen
Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.
Anwendungsbeispiel
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}Erwartetes Ergebnis
Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:298
Send 80|(ch&0F), note and velocity through the same synchronous serial path.
kitaqfc/kitaqfc/CodeGenerator.cs:4799
case "__midi_note_off": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1}, funcName);
EmitHelperStart("__midi_note_off");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("AND", Imm(0x0F));
EmitAsm("ORA", Imm(0x80));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_note_on
Sendet Note-on als drei Bytes: 0x90 | (ch & 0x0F), note, velocity.
Syntax
void __midi_note_on(u8 ch, u8 note, u8 velocity);Parameter
chMIDI-Kanal 0..15; das untere Halbbyte geht ins Statusbyte ein. Null entspricht dem angezeigten MIDI-Kanal 1.
noteMIDI-Note 0..127; 69 ist A4. Es erfolgt keine Siebenbitmaskierung; der Aufrufer hält den Bereich ein.
velocityAnschlagstärke 0..127; keine automatische Siebenbitmaskierung.
Rückgabewert
Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.
Bedingungen und Grenzen
Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.
Anwendungsbeispiel
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}Erwartetes Ergebnis
Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:296
Send 90|(ch&0F), note and velocity. Supply 7-bit data bytes; they are not masked by this helper.
kitaqfc/kitaqfc/CodeGenerator.cs:4798
case "__midi_note_on": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1}, funcName);
EmitHelperStart("__midi_note_on");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("AND", Imm(0x0F));
EmitAsm("ORA", Imm(0x90));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_out_byte
Sendet synchron ein 8N1-Byte: niedriger Startpegel, acht Datenbits beginnend mit dem niederwertigsten, hoher Stopppegel.
Syntax
void __midi_out_byte(u8 byte);Parameter
byteRohes Byte 0..255, einschließlich Status- oder Datenbytes.
Rückgabewert
Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.
Bedingungen und Grenzen
Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.
Anwendungsbeispiel
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}Erwartetes Ergebnis
Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:292
Send start-low, eight least-significant-first bits and stop-high in 57-cycle NTSC bit cells (about 31400 bps). Stop DMC DMA and mask IRQ/NMI during transfers; PAL timing and electrical adapter compatibility are not established here.
kitaqfc/kitaqfc/CodeGenerator.cs:4795
case "__midi_out_byte": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1}, funcName);
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_program_change
Sendet Program Change als 0xC0 | (ch & 0x0F), gefolgt von der Programmnummer.
Syntax
void __midi_program_change(u8 ch, u8 program);Parameter
chMIDI-Kanal 0..15; das untere Halbbyte geht ins Statusbyte ein. Null entspricht dem angezeigten MIDI-Kanal 1.
programProgramm 0..127; die Instrumentzuordnung hängt vom Empfänger ab.
Rückgabewert
Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.
Bedingungen und Grenzen
Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.
Anwendungsbeispiel
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}Erwartetes Ergebnis
Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:302
Send C0|(ch&0F) followed by the caller-supplied 7-bit program value.
kitaqfc/kitaqfc/CodeGenerator.cs:4801
case "__midi_program_change": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1}, funcName);
// Real-time MIDI commands send a single status byte through the common output routine.
EmitHelperStart("__midi_program_change");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("AND", Imm(0x0F));
EmitAsm("ORA", Imm(0xC0));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_start
Sendet ein Echtzeit-Startbyte FA, ohne Liedzeiger des Spiels zu ändern.
Syntax
void __midi_start(void);Rückgabewert
Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.
Bedingungen und Grenzen
Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.
Anwendungsbeispiel
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}Erwartetes Ergebnis
Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:306
Transmit realtime Start (FA) through the serial adapter path.
kitaqfc/kitaqfc/CodeGenerator.cs:4804
case "__midi_start": if (!Need(0)) return true; EmitAsm("LDA", Imm(0xFA)); EmitAsm("JSR", Abs("__kq_midi_out_a")); return true;
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__midi_stop
Sendet ein Echtzeit-Stopbyte FC. Dies schaltet weder APU noch zuvor gesendete Noten direkt stumm.
Syntax
void __midi_stop(void);Rückgabewert
Kein Rückgabewert. Ändert Audioregister oder Treiberzustand.
Bedingungen und Grenzen
Benötigt eine externe Schaltung von $4016 D0 zu TX und $4017 D4 zu RX. Ein normaler Controller bietet kein MIDI; diese Aufrufe spielen nicht die interne APU. Stoppen Sie DMC-Wiedergabe/DMA und sperren Sie NMI und IRQ während Transfers; vermeiden Sie gleichzeitige Port-/Arbeitsspeicherzugriffe. 57 NTSC-Zyklen pro Bit ergeben etwa 31.400 Bit/s, rund +0,5 % gegenüber 31.250. PAL-Timing und Kommunikation in beide Richtungen mit echtem Adapter sind ungeprüft.
Anwendungsbeispiel
// Controller-port byte encoding. This does not synthesize sound inside the FC.
#define SOUND_PORT_ONLY
#include "fc_sound_example.h"
void main(void) {
sound_begin("MIDI PORT BYTES");
// sound_begin has disabled DMC DMA and all internal APU voices. A compatible external circuit is required.
// Stop NMI and mask IRQ so neither can split a software-timed serial frame.
__ppu_ctrl_set(0); __irq_disable();
__midi_out_byte(0x7E);
__midi_program_change(2,5);
__midi_control_change(2,7,100);
__midi_note_on(2,69,96);
__midi_note_off(2,69,0);
__midi_clock(); __midi_start(); __midi_continue(); __midi_stop();
// The emulator's unconnected input bit is low. Reading returns zero here.
// A real input held high would block waiting for its start bit, without timeout.
sound_result[0]=__midi_in_byte();
sound_result[1]=1;
__ppu_ctrl_set(0x80); __irq_enable();
sound_end();
}Erwartetes Ergebnis
Der Bildschirm zeigt EXTERNAL PORT DATA, interne Stimmen bleiben still. Gesendet werden hexadezimal 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Die Prüfung verlangt NTSC-Sendezellen und Datenleseabstände von je 57 Zyklen. Empfangene 00 stammen vom unverbundenen Emulatoreingang und belegen keine externe Gerätekommunikation.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sound_midi.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\fc-midi.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:310
Transmit realtime Stop (FC) through the serial adapter path.
kitaqfc/kitaqfc/CodeGenerator.cs:4806
case "__midi_stop": if (!Need(0)) return true; EmitAsm("LDA", Imm(0xFC)); EmitAsm("JSR", Abs("__kq_midi_out_a")); return true;
// Sprite helpers modify the runtime OAM representation; DMA remains a separate operation.
EmitHelperStart("__kq_midi_out_a");
bool midiScratchInZeroPage = _runtimeIntrinsicTmp0Address <= 0xFF;
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(0x4016));
// Unroll so neither branch cost nor payload bits change the baud.
// Each data edge: 44 NOP cycles + LSR zp (5) + LDA/ADC (4) + STA (4).
for (int bit = 0; bit < 8; bit++)
{
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LSR", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("ADC", Imm(0));
EmitAsm("STA", Mem(0x4016));
}
// 51 delay cycles plus LDA/STA (6) place the stop bit 57 cycles later.
for (int delay = 0; delay < 24; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitMidiBitDelay(); // Hold stop high for at least one full bit cell.
EmitAsm("RTS");
EmitHelperStart("__midi_out_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__kq_midi_out_a"));
EmitAsm("RTS");
// Poll in nine-cycle steps, then sample near data-bit centers.
// IRQ/NMI masking belongs to the caller; no timeout/framing status is returned.
EmitHelperStart("__midi_in_byte");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string waitStart = NewGeneratedLabel("midi_wait_start");
_assembly.Add(Expr.Make(Tag.Label, waitStart));
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("BNE", Rel(waitStart));
// 81 cycles from the start-detect read to the first data read.
// Polling uncertainty is at most nine cycles within a 57-cycle cell.
for (int delay = 0; delay < 35; delay++) EmitAsm("NOP");
EmitAsm("BIT", Mem(CallArgBase));
for (int bit = 0; bit < 8; bit++)
{
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
EmitAsm("CMP", Imm(0x10));
EmitAsm("ROR", Mem(_runtimeIntrinsicTmp0Address));
for (int delay = 0; delay < (midiScratchInZeroPage ? 22 : 20); delay++) EmitAsm("NOP");
if (!midiScratchInZeroPage) EmitAsm("BIT", Mem(CallArgBase));
}
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Compose the channelized note-on status, then send note and velocity arguments.__mirroring_set
Ordnet die vier logischen Nametable-Bereiche den zwei Bildschirmspeicherseiten der Konsole zu, für Scrolllayouts oder die Auswahl einer Einzelseite.
Syntax
void __mirroring_set(u8 mode);Parameter
modeModusnummern hängen von der Platine ab. MMC1/SUROM: 0=untere Einzelseite, 1=obere Einzelseite, 2=vertikal, 3=horizontal. MMC3: 0=vertikal, 1=horizontal. MMC5: 0=horizontal, 1=vertikal, 2=untere Einzelseite, 3=obere Einzelseite. AxROM: 0=untere Einzelseite, 1=obere Einzelseite. FME7: 0=vertikal, 1=horizontal, 2=untere Einzelseite, 3=obere Einzelseite. MMC3/AxROM nutzen das niedrigste Bit, die anderen die beiden niedrigsten Bits.
Rückgabewert
Kein Rückgabewert.
Bedingungen und Grenzen
Die Intrinsic-Deklarationen stehen in intrinsics.h. Binden Sie für die Makros nes_mapper_irq_* den Header nes_game.h ein. Wählen Sie die Platine beim Build mit --mapper.
Vertikale Spiegelung ordnet die vier Bereiche als A B A B zu, horizontale als A A B B. Die untere Einzelseite ergibt A A A A, die obere B B B B. Das ändert die Speicherzuordnung, dreht oder kopiert aber keine Hintergrundgrafik. Nicht unterstützte Platinen einschließlich NROM mit fester Spiegelung werden beim Build abgewiesen.
MMC1 schreibt den Steuerwert $0C|mode und wählt damit ein festes oberes 16-KiB-PRG-Fenster und den 8-KiB-CHR-Modus. SUROM aktualisiert auch seine Steuerwertkopie. AxROM ändert das Bildschirmbit und behält den Laufzeit-PRG-Bankwert bei. Halten Sie direkte Mapperregister-Schreibzugriffe mit dem Laufzeit-Bankzustand konsistent.
Bei MMC5 schreibt die API $50/$44/$00/$55 nach $5105. Diese Anordnungen nutzen nur die zwei CIRAM-Seiten der Konsole. ExRAM und Füllmodus gehören nicht zu dieser API. KUROSAKI unterstützt alle sechzehn reinen CIRAM-Anordnungen, aber keine MMC5-ExRAM- oder Fülldarstellung.
Ersetzen Sie im Beispiel --mapper=mmc3 durch mmc1, mmc5, axrom oder fme7, um die jeweilige Platine zu prüfen. Das Beispiel initialisiert MMC5/FME7-Schrift-CHR-Banken über deren Platinenregister.
Anwendungsbeispiel
k=1;
for (i=0;i<4;i++) {
__mirroring_set(i);
for (j=0;j<4;j++) { mirror_result[k]=mirror_read(0x2000+(u16)j*1024); k++; }
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Die Lektion schreibt 17 in die untere physische RAM-Seite und 34 in die obere und liest dann die vier logischen Bereiche. A bezeichnet die untere, B die obere Seite. Bereiche mit demselben Buchstaben teilen RAM; ein Schreibzugriff über einen Bereich ist deshalb auch über die anderen sichtbar. Spalten sind $2000, $2400, $2800 und $2C00, Zeilen die Modi 0 bis 3. Text erscheint erst nach dem Zurücklesen.





Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\mapper_mirroring.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\mapper_mirroring.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:322
Emit the selected mapper mirroring operation; available modes and writable control depend on the board.
__mul16x8
Berechnet eine vorzeichenlose Multiplikation.
Syntax
u16 __mul16x8(u16 a, u8 b);Parameter
aVorzeichenloses Wort, 0..65535.
bVorzeichenloser Koeffizient, 0..255.
Rückgabewert
Gibt die unteren 16 Bits von a*b zurück.
Bedingungen und Grenzen
Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.
Anwendungsbeispiel
// Compare literal coefficients with values read from RAM.
result[2]=__mul16x8(513,127);
v0=513;v1=127;
result[3]=__mul16x8(v0,v1);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.
Der Ausschnitt speichert folgende 16-Bit-Werte: result[2]=65151, result[3]=65151. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chrImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:51
Return the low word of the unsigned product; overflow wraps without saturation.
kitaqfc/kitaqfc/CodeGenerator.cs:4647
case "__mul16x8": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__mul8x8_hi
Multipliziert zwei vorzeichenlose Bytes und gibt das obere Byte zurück.
Syntax
u8 __mul8x8_hi(u8 a, u8 b);Parameter
aVorzeichenloses Byte, 0..255.
bVorzeichenloses Byte, 0..255.
Rückgabewert
floor(a*b/256), im Bereich 0..254.
Anwendungsbeispiel
// Compare literal coefficients with values read from RAM.
result[0]=__mul8x8_hi(255,255);
v0=255;v1=255;
result[1]=__mul8x8_hi(v0,v1);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.
Der Ausschnitt speichert folgende 16-Bit-Werte: result[0]=254, result[1]=254. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chrImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:49
Return the high byte of the unsigned 8-by-8 product, equivalent to floor(a*b/256).
kitaqfc/kitaqfc/CodeGenerator.cs:4649
case "__mul8x8_hi": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__nametable_put
Schreibt ein Kachelbyte direkt in Nametable 0 an 0x2000 + y*32 + x. Setzt den Latch vor der Adresse zurück. Schreibt weder Kachelgrafiken noch Palettenattribute und begrenzt die Koordinaten nicht.
Syntax
void __nametable_put(u8 x, u8 y, u8 tile);Parameter
x / yKachelkoordinaten, keine Pixel. Verwenden Sie
x=0..31undy=0..29für die sichtbare Karte von 32×30 Kacheln; jede Kachel umfasst 8×8 Pixel. Die Byteargumente werden nicht begrenzt und können außerhalb des vorgesehenen Kachelbereichs schreiben.tileKachelnummer
0..255in der durch PPUCTRL gewählten Hintergrund-Mustertabelle. Wählt vorhandene CHR-Grafiken aus, keine Pixelfarbe und keinen Quellzeiger.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.
Anwendungsbeispiel
__nametable_put(2,12,1); // Blue solid marker at (16,96).Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:124
Write one tile directly at 2000+y*32+x; use x<32, y<30 for visible nametable cells.
kitaqfc/kitaqfc/CodeGenerator.cs:4820
case "__nametable_put": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1}, funcName);
void EmitHelperNametablePut()
{
EmitHelperStart("__nametable_put");
// args: x, y, tile. PPU address = $2000 + y * 32 + x.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
for (int i = 0; i < 5; i++) { EmitAsm("ASL", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("ROL", Mem(_runtimeIntrinsicTmp1Address)); }
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Imm(0x20));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(0x2002));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(0x2007));
EmitAsm("RTS");
}__nametable_put_nt
Wählt die logische Nametable nt & 3 und schreibt eine Kachel an 0x2000 + (nt & 3)*0x400 + y*32 + x. Setzt den Latch vor dem Adresspaar zurück. Durch Cartridge-Spiegelung können verschiedene logische Tabellennummern denselben physischen Speicher adressieren.
Syntax
void __nametable_put_nt(u8 nt, u8 x, u8 y, u8 tile);Parameter
ntAuswahl der logischen Nametable. Nur die unteren zwei Bits zählen (
nt & 3): 4 wählt Tabelle 0, 7 wählt Tabelle 3. Es wird kein unabhängiger Speicher reserviert und keine Mapper-Spiegelung geändert.x / yKachelkoordinaten, keine Pixel. Verwenden Sie
x=0..31undy=0..29für die sichtbare Karte von 32×30 Kacheln; jede Kachel umfasst 8×8 Pixel. Die Byteargumente werden nicht begrenzt und können außerhalb des vorgesehenen Kachelbereichs schreiben.tileKachelnummer
0..255in der durch PPUCTRL gewählten Hintergrund-Mustertabelle. Wählt vorhandene CHR-Grafiken aus, keine Pixelfarbe und keinen Quellzeiger.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.
Anwendungsbeispiel
__nametable_put_nt(4,10,12,2); // Red left-half marker at (80,96), table 0.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:126
Write one tile in nametable nt&3. Mirroring can alias logical nametables; coordinates are unchecked.
kitaqfc/kitaqfc/CodeGenerator.cs:4821
case "__nametable_put_nt": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1,1}, funcName);
void EmitHelperNametablePutNt()
{
EmitHelperStart("__nametable_put_nt");
// args: nt,x,y,tile. PPU address = $2000 + nt*$400 + y*32 + x.
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("AND", Imm(3));
EmitAsm("ASL");
EmitAsm("ASL");
EmitAsm("CLC");
EmitAsm("ADC", Imm(0x20));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
for (int i = 0; i < 5; i++) { EmitAsm("ASL", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("ROL", Mem(_runtimeIntrinsicTmp1Address)); }
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(0x2002));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(CallArgBase + 3));
EmitAsm("STA", Mem(0x2007));
EmitAsm("RTS");
}__nametable_rect
Füllt ab (x,y) ein Rechteck aus w mal h Kachelbytes in Nametable 0. Setzt Latch und Adresse für jede Zeile zurück, schreibt w Kopien von tile und erhöht den Beginn der nächsten Zeile um 32 Bytes. Palettenattribute bleiben unverändert.
Syntax
void __nametable_rect(u8 x, u8 y, u8 w, u8 h, u8 tile);Parameter
x / yKachelkoordinaten, keine Pixel. Verwenden Sie
x=0..31undy=0..29für die sichtbare Karte von 32×30 Kacheln; jede Kachel umfasst 8×8 Pixel. Die Byteargumente werden nicht begrenzt und können außerhalb des vorgesehenen Kachelbereichs schreiben.w / hBreite und Höhe in Kacheln, jeweils ein vorzeichenloses Byte. Halten Sie
x+w<=32undy+h<=30ein. Höhe null führt keine PPU-Zugriffe aus. Breite null schreibt keine Kachelbytes, setzt aber den Latch zurück und setzt allehZeilenadressen. Verwenden Sie für horizontale Zeilen PPU-Schrittweite 1.tileKachelnummer
0..255in der durch PPUCTRL gewählten Hintergrund-Mustertabelle. Wählt vorhandene CHR-Grafiken aus, keine Pixelfarbe und keinen Quellzeiger.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.
Anwendungsbeispiel
__nametable_rect(2,6,8,4,1); // Solid green rectangle at (16,48), 64x32 pixels.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:129
Fill rows of nametable zero directly, advancing each row start by 32 bytes. Keep the rectangle in bounds and use PPU increment one; this does not enqueue or clip.
kitaqfc/kitaqfc/CodeGenerator.cs:4822
case "__nametable_rect": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1,1,1}, funcName);
void EmitHelperNametableRect()
{
EmitHelperStart("__nametable_rect");
// args: x,y,w,h,tile; writes tile-filled rectangle to nametable 0.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
for (int i = 0; i < 5; i++) { EmitAsm("ASL", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("ROL", Mem(_runtimeIntrinsicTmp1Address)); }
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("CLC"); EmitAsm("ADC", Mem(CallArgBase)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("ADC", Imm(0x20)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 3)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string rowLoop = NewGeneratedLabel("nt_rect_row");
string done = NewGeneratedLabel("nt_rect_done");
_assembly.Add(Expr.Make(Tag.Label, rowLoop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("BEQ", Rel(done));
EmitAsm("LDA", Mem(0x2002)); EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("STA", Mem(0x2006)); EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("STA", Mem(0x2006));
EmitAsm("LDY", Imm(0));
string colLoop = NewGeneratedLabel("nt_rect_col");
string rowDone = NewGeneratedLabel("nt_rect_row_done");
_assembly.Add(Expr.Make(Tag.Label, colLoop));
EmitAsm("CPY", Mem(CallArgBase + 2)); EmitAsm("BEQ", Rel(rowDone));
EmitAsm("LDA", Mem(CallArgBase + 4)); EmitAsm("STA", Mem(0x2007)); EmitAsm("INY"); EmitAsm("BNE", Rel(colLoop));
_assembly.Add(Expr.Make(Tag.Label, rowDone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("CLC"); EmitAsm("ADC", Imm(32)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("BCC", Rel(rowLoop + "_nohi"));
EmitAsm("INC", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, rowLoop + "_nohi"));
EmitAsm("DEC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("JMP", Abs(rowLoop));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__nametable_rect_nt
Füllt in der logischen Nametable nt & 3 jeweils w Kacheln über h Zeilen. Die Anfangsadresse lautet 0x2000 + (nt & 3)*0x400 + y*32 + x; jeder Zeilenbeginn rückt um 32 Bytes weiter. Schreibt nur Kachelbytes, ohne Attribute zu ändern, Grenzen abzuschneiden oder Spiegelung einzustellen.
Syntax
void __nametable_rect_nt(u8 nt, u8 x, u8 y, u8 w, u8 h, u8 tile);Parameter
ntAuswahl der logischen Nametable. Nur die unteren zwei Bits zählen (
nt & 3): 4 wählt Tabelle 0, 7 wählt Tabelle 3. Es wird kein unabhängiger Speicher reserviert und keine Mapper-Spiegelung geändert.x / yKachelkoordinaten, keine Pixel. Verwenden Sie
x=0..31undy=0..29für die sichtbare Karte von 32×30 Kacheln; jede Kachel umfasst 8×8 Pixel. Die Byteargumente werden nicht begrenzt und können außerhalb des vorgesehenen Kachelbereichs schreiben.w / hBreite und Höhe in Kacheln, jeweils ein vorzeichenloses Byte. Halten Sie
x+w<=32undy+h<=30ein. Höhe null führt keine PPU-Zugriffe aus. Breite null schreibt keine Kachelbytes, setzt aber den Latch zurück und setzt allehZeilenadressen. Verwenden Sie für horizontale Zeilen PPU-Schrittweite 1.tileKachelnummer
0..255in der durch PPUCTRL gewählten Hintergrund-Mustertabelle. Wählt vorhandene CHR-Grafiken aus, keine Pixelfarbe und keinen Quellzeiger.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.
Anwendungsbeispiel
__nametable_rect_nt(4,18,6,8,4,3); // nt&3=0; red triangle tiles at (144,48).Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:131
Fill a rectangle in nametable nt&3 with the same unchecked, direct-write row loop.
kitaqfc/kitaqfc/CodeGenerator.cs:4823
case "__nametable_rect_nt": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1,1,1,1}, funcName);
void EmitHelperNametableRectNt()
{
EmitHelperStart("__nametable_rect_nt");
// args: nt,x,y,w,h,tile.
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("AND", Imm(3));
EmitAsm("ASL");
EmitAsm("ASL");
EmitAsm("CLC");
EmitAsm("ADC", Imm(0x20));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
for (int i = 0; i < 5; i++) { EmitAsm("ASL", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("ROL", Mem(_runtimeIntrinsicTmp1Address)); }
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("CLC"); EmitAsm("ADC", Mem(CallArgBase + 1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 4)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string rowLoop = NewGeneratedLabel("nt_rect_nt_row");
string done = NewGeneratedLabel("nt_rect_nt_done");
_assembly.Add(Expr.Make(Tag.Label, rowLoop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("BEQ", Rel(done));
EmitAsm("LDA", Mem(0x2002)); EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("STA", Mem(0x2006)); EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("STA", Mem(0x2006));
EmitAsm("LDY", Imm(0));
string colLoop = NewGeneratedLabel("nt_rect_nt_col");
string rowDone = NewGeneratedLabel("nt_rect_nt_row_done");
_assembly.Add(Expr.Make(Tag.Label, colLoop));
EmitAsm("CPY", Mem(CallArgBase + 3)); EmitAsm("BEQ", Rel(rowDone));
EmitAsm("LDA", Mem(CallArgBase + 5)); EmitAsm("STA", Mem(0x2007)); EmitAsm("INY"); EmitAsm("BNE", Rel(colLoop));
_assembly.Add(Expr.Make(Tag.Label, rowDone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("CLC"); EmitAsm("ADC", Imm(32)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("BCC", Rel(rowLoop + "_nohi"));
EmitAsm("INC", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, rowLoop + "_nohi"));
EmitAsm("DEC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("JMP", Abs(rowLoop));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__nmi_disable
Deaktiviert die NMI-Erzeugung der PPU: Bit 7 der PPUCTRL-Kopie des Compilers wird gelöscht und der Wert nach $2000 geschrieben. Die übrigen Bits bleiben erhalten. IRQ-Maske und Bildausgabe werden nicht verändert.
Syntax
void __nmi_disable(void);Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Diese Funktionen verwenden die PPUCTRL-Kopie und lesen die Hardware nicht zurück. Direkte Schreibzugriffe auf $2000 aktualisieren die Kopie nicht. Bei gesetztem VBlank-Flag kann das Einschalten sofort NMI auslösen. Das Abschalten löscht keine bereits von der CPU erfasste Anforderung.
Anwendungsbeispiel
__nmi_disable();
before_count=__nmi_ready();
for(i=0;i<2000;i++)frozen=__nmi_ready();
frozen=(u8)(frozen-before_count); // No default-handler advances while off.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm unterscheidet zwischen der IRQ-Maske der CPU und der NMI-Steuerung der PPU. Blau auf Schwarz müssen IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 und RESTORED I = 000 erscheinen. Die Werte zeigen nur das extrahierte I-Bit, nicht das ganze Statusbyte. FAILED CHECKS muss 000 sein.
Mindestens 360 Emulatorframes ausführen, damit die Demonstration mit 256 Warteaufrufen abgeschlossen wird. NMI DELTA 3 WAITS muss trotz IRQ-Sperre 003 ergeben; NMI DELTA 256 WAITS wird durch den Byteumlauf zu 000. NMI OFF DELTA muss ebenfalls 000 sein. Das Programm verwendet den Standardhandler ohne runtime.c. Weitere 21 Zustandsprüfungen erfassen unter anderem die Wiederherstellung der Statusbits sowie zeitlich begrenzt beobachtete Wartefälle bei deaktivierter NMI oder leerem eigenem Handler. Geprüft wurde KUROSAKI/NROM, keine echte Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\interrupt_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\interrupt_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:174
Clear bit 7 in the PPUCTRL shadow and write it to hardware; CPU IRQ masking is separate.
kitaqfc/kitaqfc/CodeGenerator.cs:4730
case "__nmi_disable":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(_runtimePpuCtrlShadowAddress)); EmitAsm("AND", Imm(0x7F)); EmitAsm("STA", Mem(_runtimePpuCtrlShadowAddress)); EmitAsm("STA", Mem(0x2000)); return true;__nmi_enable
Aktiviert die von der PPU erzeugte NMI: Bit 7 der vom Compiler geführten PPUCTRL-Kopie wird gesetzt und der Wert nach $2000 geschrieben. Die übrigen Bits dieser Kopie und die IRQ-Maske der CPU bleiben erhalten. Hintergrund- und Spriteausgabe werden dadurch nicht eingeschaltet.
Syntax
void __nmi_enable(void);Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Diese Funktionen verwenden die PPUCTRL-Kopie und lesen die Hardware nicht zurück. Direkte Schreibzugriffe auf $2000 aktualisieren die Kopie nicht. Bei gesetztem VBlank-Flag kann das Einschalten sofort NMI auslösen. Das Abschalten löscht keine bereits von der CPU erfasste Anforderung.
Anwendungsbeispiel
__nmi_enable(); // Preserve the PPUCTRL shadow's other bits; CPU I stays set.
__nmi_wait(); // The default NMI handler still runs with IRQ masked.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm unterscheidet zwischen der IRQ-Maske der CPU und der NMI-Steuerung der PPU. Blau auf Schwarz müssen IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 und RESTORED I = 000 erscheinen. Die Werte zeigen nur das extrahierte I-Bit, nicht das ganze Statusbyte. FAILED CHECKS muss 000 sein.
Mindestens 360 Emulatorframes ausführen, damit die Demonstration mit 256 Warteaufrufen abgeschlossen wird. NMI DELTA 3 WAITS muss trotz IRQ-Sperre 003 ergeben; NMI DELTA 256 WAITS wird durch den Byteumlauf zu 000. NMI OFF DELTA muss ebenfalls 000 sein. Das Programm verwendet den Standardhandler ohne runtime.c. Weitere 21 Zustandsprüfungen erfassen unter anderem die Wiederherstellung der Statusbits sowie zeitlich begrenzt beobachtete Wartefälle bei deaktivierter NMI oder leerem eigenem Handler. Geprüft wurde KUROSAKI/NROM, keine echte Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\interrupt_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\interrupt_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:172
Set bit 7 in the PPUCTRL shadow and write it to hardware, preserving the other shadow bits.
kitaqfc/kitaqfc/CodeGenerator.cs:4727
case "__nmi_enable":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(_runtimePpuCtrlShadowAddress)); EmitAsm("ORA", Imm(0x80)); EmitAsm("STA", Mem(_runtimePpuCtrlShadowAddress)); EmitAsm("STA", Mem(0x2000)); return true;__nmi_ready
Liest den aktuellen NMI-Zähler des Compilers, ohne zu warten oder ihn zu löschen. Trotz des Namens ready ist das Ergebnis ein Zählerbyte und kein boolesches Bereitschaftsflag. Ein Vergleich mit einem gespeicherten Wert zeigt den Fortschritt des Standard-NMI-Handlers.
Syntax
u8 __nmi_ready(void);Rückgabewert
Ein vorzeichenloses Byte von 0 bis 255, das der Standard-NMI-Handler modulo 256 erhöht. Null ist gültig. Nach 256 Schritten stimmt der aktuelle Wert wieder mit dem gespeicherten überein; seltenes Abfragen erlaubt keine unbegrenzt lange Zeitmessung.
Bedingungen und Grenzen
Dieser Zähler gehört zum Standard-NMI-Handler des Compilers. Eine Definition von __nes_nmi ersetzt ihn. runtime.c verwendet eigene nes_nmi_counter und nes_wait_nmi. Ein leerer eigener Handler erhöht den Compilerzähler nicht; __nmi_wait kehrt dann nicht zurück. NMI aktivieren und außerhalb des NMI-Handlers warten.
Anwendungsbeispiel
before_count=__nmi_ready(); // A wrapping byte, not a Boolean ready flag.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm unterscheidet zwischen der IRQ-Maske der CPU und der NMI-Steuerung der PPU. Blau auf Schwarz müssen IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 und RESTORED I = 000 erscheinen. Die Werte zeigen nur das extrahierte I-Bit, nicht das ganze Statusbyte. FAILED CHECKS muss 000 sein.
Mindestens 360 Emulatorframes ausführen, damit die Demonstration mit 256 Warteaufrufen abgeschlossen wird. NMI DELTA 3 WAITS muss trotz IRQ-Sperre 003 ergeben; NMI DELTA 256 WAITS wird durch den Byteumlauf zu 000. NMI OFF DELTA muss ebenfalls 000 sein. Das Programm verwendet den Standardhandler ohne runtime.c. Weitere 21 Zustandsprüfungen erfassen unter anderem die Wiederherstellung der Statusbits sowie zeitlich begrenzt beobachtete Wartefälle bei deaktivierter NMI oder leerem eigenem Handler. Geprüft wurde KUROSAKI/NROM, keine echte Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\interrupt_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\interrupt_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:170
Return the current wrapping NMI counter byte, not a Boolean ready flag.
kitaqfc/kitaqfc/CodeGenerator.cs:4740
case "__nmi_ready": if (!Need(0)) return true; EmitAsm("LDA", Mem(_runtimeNmiCounterAddress)); return true;
// Reset OAMADDR and transfer page 02; the page-selecting variant evaluates its own byte argument.
// The default NMI executes the queued VRAM work and increments the runtime frame counter before RTI.
if (!_functions.ContainsKey("__nes_nmi"))
{
EmitPlacement(0, true);
_assembly.Add(Expr.Make(Tag.Function, "__kq_nmi_default"));
// NMI may interrupt argument evaluation, a pointer dispatch or an ordinary expression.
// Preserve registers and every shared byte touched by the queue executor; RTI restores P.
EmitAsm("PHA"); EmitAsm("TXA"); EmitAsm("PHA"); EmitAsm("TYA"); EmitAsm("PHA");
int[] queueScratch = { CallArgBase, CallArgBase + 1, CallArgBase + 2,
_runtimeIntrinsicTmp0Address, _runtimeIntrinsicTmp1Address, _runtimeIntrinsicTmp2Address };
foreach (int address in queueScratch) { EmitAsm("LDA", Mem(address)); EmitAsm("PHA"); }
// The optional library hook preserves its own registers and uses no
// compiler scratch. It consumes only RAM records and fixed ROM tables.
if (_functions.ContainsKey("__nes_audio_vblank_tick"))
EmitAsm("JSR", Abs("__nes_audio_vblank_tick"));
EmitAsm("JSR", Abs("__vramq_exec"));
EmitAsm("INC", Mem(_runtimeNmiCounterAddress));
foreach (int address in queueScratch.Reverse()) { EmitAsm("PLA"); EmitAsm("STA", Mem(address)); }
EmitAsm("PLA"); EmitAsm("TAY"); EmitAsm("PLA"); EmitAsm("TAX"); EmitAsm("PLA");
EmitAsm("RTI");
}__nmi_wait
Liest beim Aufruf den NMI-Zähler des Compilers und wartet in einer Schleife auf eine Änderung. Beim Standardhandler bedeutet dies den Abschluss eines späteren Handleraufrufs einschließlich der freigegebenen VRAM-Aufträge. Der Aufruf aktiviert keine NMI, gibt keine Aufträge frei und hat kein Zeitlimit.
Syntax
void __nmi_wait(void);Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Dieser Zähler gehört zum Standard-NMI-Handler des Compilers. Eine Definition von __nes_nmi ersetzt ihn. runtime.c verwendet eigene nes_nmi_counter und nes_wait_nmi. Ein leerer eigener Handler erhöht den Compilerzähler nicht; __nmi_wait kehrt dann nicht zurück. NMI aktivieren und außerhalb des NMI-Handlers warten.
Anwendungsbeispiel
__nmi_wait();__nmi_wait();__nmi_wait();
delta=(u8)(__nmi_ready()-before_count); // Three completed default NMIs.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm unterscheidet zwischen der IRQ-Maske der CPU und der NMI-Steuerung der PPU. Blau auf Schwarz müssen IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 und RESTORED I = 000 erscheinen. Die Werte zeigen nur das extrahierte I-Bit, nicht das ganze Statusbyte. FAILED CHECKS muss 000 sein.
Mindestens 360 Emulatorframes ausführen, damit die Demonstration mit 256 Warteaufrufen abgeschlossen wird. NMI DELTA 3 WAITS muss trotz IRQ-Sperre 003 ergeben; NMI DELTA 256 WAITS wird durch den Byteumlauf zu 000. NMI OFF DELTA muss ebenfalls 000 sein. Das Programm verwendet den Standardhandler ohne runtime.c. Weitere 21 Zustandsprüfungen erfassen unter anderem die Wiederherstellung der Statusbits sowie zeitlich begrenzt beobachtete Wartefälle bei deaktivierter NMI oder leerem eigenem Handler. Geprüft wurde KUROSAKI/NROM, keine echte Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\interrupt_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\interrupt_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:168
NMI / IRQ Spin until the runtime NMI counter changes; the configured handler must advance it. No timeout is emitted.
kitaqfc/kitaqfc/CodeGenerator.cs:4738
case "__nmi_wait": if (!Need(0)) return true; EmitAsm("JSR", Abs("__nmi_wait")); return true;
// Return the current NMI counter byte; callers decide how to compare it with their previous value.
void EmitHelperNmiWait()
{
EmitHelperStart("__nmi_wait");
EmitAsm("LDA", Mem(_runtimeNmiCounterAddress));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
string loop = NewGeneratedLabel("nmi_wait_loop");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LDA", Mem(_runtimeNmiCounterAddress));
EmitAsm("CMP", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("BEQ", Rel(loop));
EmitAsm("RTS");
}
// The default NMI executes the queued VRAM work and increments the runtime frame counter before RTI.
if (!_functions.ContainsKey("__nes_nmi"))
{
EmitPlacement(0, true);
_assembly.Add(Expr.Make(Tag.Function, "__kq_nmi_default"));
// NMI may interrupt argument evaluation, a pointer dispatch or an ordinary expression.
// Preserve registers and every shared byte touched by the queue executor; RTI restores P.
EmitAsm("PHA"); EmitAsm("TXA"); EmitAsm("PHA"); EmitAsm("TYA"); EmitAsm("PHA");
int[] queueScratch = { CallArgBase, CallArgBase + 1, CallArgBase + 2,
_runtimeIntrinsicTmp0Address, _runtimeIntrinsicTmp1Address, _runtimeIntrinsicTmp2Address };
foreach (int address in queueScratch) { EmitAsm("LDA", Mem(address)); EmitAsm("PHA"); }
// The optional library hook preserves its own registers and uses no
// compiler scratch. It consumes only RAM records and fixed ROM tables.
if (_functions.ContainsKey("__nes_audio_vblank_tick"))
EmitAsm("JSR", Abs("__nes_audio_vblank_tick"));
EmitAsm("JSR", Abs("__vramq_exec"));
EmitAsm("INC", Mem(_runtimeNmiCounterAddress));
foreach (int address in queueScratch.Reverse()) { EmitAsm("PLA"); EmitAsm("STA", Mem(address)); }
EmitAsm("PLA"); EmitAsm("TAY"); EmitAsm("PLA"); EmitAsm("TAX"); EmitAsm("PLA");
EmitAsm("RTI");
}__oam_clear
Blendet alle 64 Sprites im Schattenpuffer der Seite 02 aus, indem jedes Y-Byte auf 0xF0 gesetzt wird. Kachelnummern, Attribute und X-Koordinaten bleiben erhalten.
Syntax
void __oam_clear(void);Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Diese Operationen ändern den CPU-RAM bei 0x0200–0x02FF, nicht sofort das Hardware-OAM. Übertragen Sie die fertige Seite mit __oam_dma. Belegungsflags, Koordinatenfelder und Zähler der Sprite-Bibliothek werden nicht mitgeführt. Interrupt-Routinen dürfen eine noch unvollständige Seite weder ändern noch übertragen.
Anwendungsbeispiel
__sprite_set(63,144,111,128,0); // Seed the CLEAR row with a visible entry.
__oam_clear(); // Set every Y byte to 240; tile, attributes and X are retained.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.
TRANSFER zeigt einen Pfeil bei (144,128). Dieses Programm überträgt Seite 02 direkt mit __oam_dma, einschließlich aller durch die einzelnen Sprite-Operationen vorbereiteten Felder.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:190
Hide all 64 page-02 shadow sprites by writing Y=F0; hardware OAM changes only after DMA.
EmitHelperStart("__oam_clear");
EmitAsm("LDX", Imm(0));
EmitAsm("LDA", Imm(0xF0));
string loop = NewGeneratedLabel("oam_clear_loop");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("STA", new AsmOperand(0x0200, AddressMode.AbsoluteX));
EmitAsm("INX"); EmitAsm("INX"); EmitAsm("INX"); EmitAsm("INX");
EmitAsm("BNE", Rel(loop));
EmitAsm("RTS");__oam_dma
Überträgt die vollständige Schattenseite 0x0200–0x02FF mit 256 Byte ab Eintrag 0 in das Hardware-OAM. Die CPU pausiert während der Übertragung und setzt ihre Arbeit danach fort.
Syntax
void __oam_dma(void);Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Rufen Sie die Funktion während des VBlank oder bei deaktivierter Bildausgabe auf; keine der DMA-Funktionen wartet auf VBlank. Beide setzen OAMADDR vor der Übertragung auf null. Die Hardware hält die CPU normalerweise 513 oder 514 Zyklen an; DMC-Aktivität kann dies verlängern. KUROSAKI nähert die Dauer mit 513 Zyklen an, daher belegt die Bildprüfung keine exakten Hardware-Zeiten. KQ2421 kann trotz ausdrücklichem Warten auf VBlank erscheinen, weil der Compiler anhand des Aufrufernamens prüft.
Anwendungsbeispiel
m_wait();__oam_dma(); // Transfer all 256 bytes from page 02 at VBlank.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.
TRANSFER zeigt einen Pfeil bei (144,128). Dieses Programm überträgt Seite 02 direkt mit __oam_dma, einschließlich aller durch die einzelnen Sprite-Operationen vorbereiteten Felder.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:186
OAM / input Set OAMADDR to zero and start DMA from CPU page 02; arrange safe PPU timing in the caller.
case "__oam_dma": if (!Need(0)) return true; EmitAsm("LDA", Imm(0)); EmitAsm("STA", Mem(0x2003)); EmitAsm("LDA", Imm(0x02)); EmitAsm("STA", Mem(0x4014)); return true;__oam_dma_page
Überträgt 256 Byte aus der gewählten CPU-Speicherseite ab Eintrag 0 in das Hardware-OAM. So lässt sich eine separat vorbereitete OAM-Seite verwenden, ohne sie zuvor in Seite 02 zu kopieren. Die Quelldaten bleiben unverändert.
Syntax
void __oam_dma_page(u8 page);Parameter
pageOberes Byte der CPU-Quelladresse: Übergeben Sie für
0x0400–0x04FFden Wert4, nicht0x0400. Stellen Sie eine vollständige lesbare Seite mit 256 Byte bereit und halten Sie die nötige Bank eingeblendet. Vermeiden Sie Seiten mit E/A-Registern, deren Lesezugriffe Nebenwirkungen haben können. Die Quelle wird nicht geprüft.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Rufen Sie die Funktion während des VBlank oder bei deaktivierter Bildausgabe auf; keine der DMA-Funktionen wartet auf VBlank. Beide setzen OAMADDR vor der Übertragung auf null. Die Hardware hält die CPU normalerweise 513 oder 514 Zyklen an; DMC-Aktivität kann dies verlängern. KUROSAKI nähert die Dauer mit 513 Zyklen an, daher belegt die Bildprüfung keine exakten Hardware-Zeiten. KQ2421 kann trotz ausdrücklichem Warten auf VBlank erscheinen, weil der Compiler anhand des Aufrufernamens prüft.
Anwendungsbeispiel
m_wait();__oam_dma_page(4); // The argument is a page number, not 0x0400.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.
Diese Variante kopiert die vorbereitete Seite 02 in ein separates Array bei 0x0400 und setzt nur die Kachel von Eintrag 7 auf 129. __oam_dma_page(4) muss neben TRANSFER bei (144,128) ein grünes Quadrat anzeigen, während Seite 02 weiterhin die Pfeilkachel 128 enthält. Alle anderen Zeilen entsprechen dem Standardbeispiel. Die Prüfung vergleicht sämtliche 256 OAM-Bytes mit Seite 04 und bestätigt, dass Seite 02 unverändert blieb.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_page.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_page.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:188
Set OAMADDR to zero and DMA 256 bytes from page<<8. The source page must remain readable.
case "__oam_dma_page": if (!Need(1)) return true; EmitAsm("LDA", Imm(0)); EmitAsm("STA", Mem(0x2003)); Load1(args[0]); EmitAsm("STA", Mem(0x4014)); return true;__pad_buttons
Behält Bits 0–3 des übergebenen NES-Zustands (pad & 0x0F): A, B, Select und Start. Filtert nur einen bereits gelesenen Wert und greift nicht auf den Controller zu.
Syntax
u8 __pad_buttons(u8 pad);Parameter
padEin bereits gelesener 8-Bit-NES-Pad-Wert, etwa das Ergebnis von
__pad_read1_safe(). Übergib den Wert selbst, keinen Zeiger, keine Portnummer und keine logischeinput_*-Maske.
Rückgabewert
Eine Maske von 0 bis 15 in der beschriebenen Bitreihenfolge. Mehrere Bits können gesetzt sein; eine Taste mit result & bit prüfen, statt das gesamte Ergebnis mit 1 zu vergleichen.
Bedingungen und Grenzen
Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.
Anwendungsbeispiel
buttons=__pad_buttons(raw1); // Low nibble of 129: 1, the A button.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Dieses Programm vergleicht beide Controlleranschlüsse, einfache und auf Übereinstimmung geprüfte Lesevorgänge, Maskenfilter und Kurzmakros. Halten Sie A + Rechts auf Controller 1 und B + Links auf Controller 2 gedrückt. RAW P1, SAFE P1 und ALIAS P1 sind 129; die drei P2-Werte sind 66. BUTTONS P1 ist 1 und DIRS P1 ist 128: Der Richtungsfilter behält die oberen Bitpositionen bei. FAILED CHECKS muss 0 sein. Die erfassten Werte bleiben sichtbar.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\input_raw.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\input_raw.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-disasm --no-cacheImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:213
Return pad & 0F for A/B/Select/Start; this masks a supplied snapshot and performs no polling.
__pad_dirs
Behält Bits 4–7 des NES-Zustands (pad & 0xF0): oben, unten, links und rechts. Die Bitpositionen bleiben erhalten; rechts allein liefert 128, nicht 8. Fragt den Controller nicht ab.
Syntax
u8 __pad_dirs(u8 pad);Parameter
padEin bereits gelesener 8-Bit-NES-Pad-Wert, etwa das Ergebnis von
__pad_read1_safe(). Übergib den Wert selbst, keinen Zeiger, keine Portnummer und keine logischeinput_*-Maske.
Rückgabewert
Der übergebene Wert UND 0xF0: 0, 16, 32, …, 240. Die Richtungsbits bleiben in Bits 4–7.
Bedingungen und Grenzen
Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.
Anwendungsbeispiel
directions=__pad_dirs(raw1); // High nibble retained in place: 128, not 8.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Dieses Programm vergleicht beide Controlleranschlüsse, einfache und auf Übereinstimmung geprüfte Lesevorgänge, Maskenfilter und Kurzmakros. Halten Sie A + Rechts auf Controller 1 und B + Links auf Controller 2 gedrückt. RAW P1, SAFE P1 und ALIAS P1 sind 129; die drei P2-Werte sind 66. BUTTONS P1 ist 1 und DIRS P1 ist 128: Der Richtungsfilter behält die oberen Bitpositionen bei. FAILED CHECKS muss 0 sein. Die erfassten Werte bleiben sichtbar.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\input_raw.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\input_raw.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-disasm --no-cacheImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:215
Return pad & F0 in the original bit positions; this does not normalize directions to bits 0..3.
__pad_read1
Übernimmt den Zustand beider Controller durch Schreiben von 1 und anschließend 0 nach $4016 und liest die acht seriellen D0-Bits von Controller 1 an $4016. Liefert einen Haltezustand im NES-Format, ohne Wiederholung oder Flankenermittlung.
Syntax
u8 __pad_read1(void);Rückgabewert
Eine 8-Bit-Haltemaske in roher NES-Reihenfolge. Jedes gesetzte Bit steht für eine gedrückte Taste, 0 für keine. Kein auf 0/1 normalisierter Wahrheitswert.
Bedingungen und Grenzen
Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.
Dieser Aufruf liest einen neuen Hardwarezustand. Er aktualisiert weder den gespeicherten input_*- noch den nes_pad1_*-Zustand und erzeugt keine Druck-, Loslass- oder Wiederholungsereignisse. Speichere das Ergebnis und vergleiche es bei Bedarf mit deiner vorherigen Maske.
Anwendungsbeispiel
raw1=__pad_read1(); // Controller 1: A (1) | Right (128) = 129.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Dieses Programm vergleicht beide Controlleranschlüsse, einfache und auf Übereinstimmung geprüfte Lesevorgänge, Maskenfilter und Kurzmakros. Halten Sie A + Rechts auf Controller 1 und B + Links auf Controller 2 gedrückt. RAW P1, SAFE P1 und ALIAS P1 sind 129; die drei P2-Werte sind 66. BUTTONS P1 ist 1 und DIRS P1 ist 128: Der Richtungsfilter behält die oberen Bitpositionen bei. FAILED CHECKS muss 0 sein. Die erfassten Werte bleiben sichtbar.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\input_raw.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\input_raw.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-disasm --no-cacheImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:205
Latch and sample eight D0 bits from port 1; bit order is A,B,Select,Start,Up,Down,Left,Right.
__pad_read1_d1
Liest acht serielle Bits vom Erweiterungseingang D1 an $4016. Schreibt nacheinander 1 und 0 nach $4016, um die Zustände zu übernehmen, liest Bit 1 achtmal und legt die Werte der Reihe nach in Ergebnisbits 0–7 ab. Dies ist ein einzelner Lesevorgang auf D1 von Anschluss 1; weder wird D0 des normalen Controllers gelesen noch auf übereinstimmende Wiederholungswerte geprüft.
Syntax
u8 __pad_read1_d1(void);Rückgabewert
Eine 8-Bit-Haltemaske in roher NES-Reihenfolge. Jedes gesetzte Bit steht für eine gedrückte Taste, 0 für keine. Kein auf 0/1 normalisierter Wahrheitswert.
Bedingungen und Grenzen
Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.
Verwenden Sie ein Peripheriegerät mit einem kompatiblen seriellen Achtbit-Controllerstrom auf der gewählten D1-Leitung. Die Funktion erkennt das Gerät nicht und implementiert nicht jedes Erweiterungsprotokoll. Jeder Aufruf übernimmt beide Controllerströme neu; beenden Sie einen vollständigen Lesevorgang, bevor Sie den nächsten beginnen. Die Ergebnisse sind neue Momentaufnahmen und aktualisieren weder input_* noch nes_pad1_*.
Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.
Anwendungsbeispiel
d1_first=__pad_read1_d1();Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Dieses Programm vergleicht normale D0-Eingaben mit D1 und dem digitalen Mikrofonpegel. Halten Sie A auf Controller 1 und B auf Controller 2 bis zur Erfassung gedrückt. NORMAL P1 und NORMAL P2 zeigen 001 und 002. Im beigefügten KUROSAKI-Lauf zeigen D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL und MIC DIRECT jeweils 000, weil diese Leitungen nicht emuliert werden. Die erfassten Werte bleiben anschließend stehen. Mit geeigneten Peripheriegeräten und implementierten Signalleitungen hängen die Werte von den tatsächlichen Eingaben ab; deuten Sie sie anhand der obigen Bitbelegung, statt diese Nullen zu erwarten.
Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\expansion_input.c -I .\kitaq-docs\samples -I .\kitaqfc\lib -o .\out\expansion_input.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:223
Famicom/NES expansion input devices. D1 readers are for expansion-port controllers/joysticks that expose a standard 8-bit joypad stream on data line D1 instead of D0. __joypad2p_voice() / __mic_read2p() return the Famicom 2P microphone bit. Latch and read an eight-button stream from port-1 data line D1 rather than D0.
__pad_read1_safe
Fragt Controller 1 durch wiederholte Paare vollständiger __pad_read1()-Aufrufe ab. Liefert die NES-Maske, sobald beide Werte eines Paares übereinstimmen. Das Ergebnis ist der Haltezustand, keine neue Druckflanke.
Syntax
u8 __pad_read1_safe(void);Rückgabewert
Eine 8-Bit-Haltemaske in roher NES-Reihenfolge. Jedes gesetzte Bit steht für eine gedrückte Taste, 0 für keine. Kein auf 0/1 normalisierter Wahrheitswert.
Bedingungen und Grenzen
Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.
Dieser Aufruf liest einen neuen Hardwarezustand. Er aktualisiert weder den gespeicherten input_*- noch den nes_pad1_*-Zustand und erzeugt keine Druck-, Loslass- oder Wiederholungsereignisse. Speichere das Ergebnis und vergleiche es bei Bedarf mit deiner vorherigen Maske.
Die sichere Abfrage wiederholt Paare vollständiger Lesevorgänge, bis beide Masken übereinstimmen. Jeder Lesevorgang strobt $4016 und setzt die serielle Leseposition beider Controller zurück. Die Zahl der Versuche ist unbegrenzt. Diese Prüfung belegt keine Kompatibilität mit sämtlichen DMC-/Peripheriekombinationen.
Anwendungsbeispiel
safe1=__pad_read1_safe(); // Repeat paired controller-1 reads until they agree.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Dieses Programm vergleicht beide Controlleranschlüsse, einfache und auf Übereinstimmung geprüfte Lesevorgänge, Maskenfilter und Kurzmakros. Halten Sie A + Rechts auf Controller 1 und B + Links auf Controller 2 gedrückt. RAW P1, SAFE P1 und ALIAS P1 sind 129; die drei P2-Werte sind 66. BUTTONS P1 ist 1 und DIRS P1 ist 128: Der Richtungsfilter behält die oberen Bitpositionen bei. FAILED CHECKS muss 0 sein. Die erfassten Werte bleiben sichtbar.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\input_raw.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\input_raw.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-disasm --no-cacheImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:209
Repeat pairs of port-1 polls until both bytes match; the retry loop has no fixed limit.
__pad_read2
Übernimmt den Zustand beider Controller durch Schreiben von 1 und anschließend 0 nach $4016 und liest die acht seriellen D0-Bits von Controller 2 an $4017. Liefert einen Haltezustand im NES-Format, ohne Wiederholung oder Flankenermittlung.
Syntax
u8 __pad_read2(void);Rückgabewert
Eine 8-Bit-Haltemaske in roher NES-Reihenfolge. Jedes gesetzte Bit steht für eine gedrückte Taste, 0 für keine. Kein auf 0/1 normalisierter Wahrheitswert.
Bedingungen und Grenzen
Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.
Dieser Aufruf liest einen neuen Hardwarezustand. Er aktualisiert weder den gespeicherten input_*- noch den nes_pad1_*-Zustand und erzeugt keine Druck-, Loslass- oder Wiederholungsereignisse. Speichere das Ergebnis und vergleiche es bei Bedarf mit deiner vorherigen Maske.
Anwendungsbeispiel
raw2=__pad_read2(); // Controller 2: B (2) | Left (64) = 66.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Dieses Programm vergleicht beide Controlleranschlüsse, einfache und auf Übereinstimmung geprüfte Lesevorgänge, Maskenfilter und Kurzmakros. Halten Sie A + Rechts auf Controller 1 und B + Links auf Controller 2 gedrückt. RAW P1, SAFE P1 und ALIAS P1 sind 129; die drei P2-Werte sind 66. BUTTONS P1 ist 1 und DIRS P1 ist 128: Der Richtungsfilter behält die oberen Bitpositionen bei. FAILED CHECKS muss 0 sein. Die erfassten Werte bleiben sichtbar.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\input_raw.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\input_raw.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-disasm --no-cacheImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:207
Latch and sample eight D0 bits from port 2 in the same NES button order.
__pad_read2_d1
Liest acht serielle Bits vom Erweiterungseingang D1 an $4017. Schreibt nacheinander 1 und 0 nach $4016, um die Zustände zu übernehmen, liest Bit 1 achtmal und legt die Werte der Reihe nach in Ergebnisbits 0–7 ab. Dies ist ein einzelner Lesevorgang auf D1 von Anschluss 2; weder wird D0 des normalen Controllers gelesen noch auf übereinstimmende Wiederholungswerte geprüft.
Syntax
u8 __pad_read2_d1(void);Rückgabewert
Eine 8-Bit-Haltemaske in roher NES-Reihenfolge. Jedes gesetzte Bit steht für eine gedrückte Taste, 0 für keine. Kein auf 0/1 normalisierter Wahrheitswert.
Bedingungen und Grenzen
Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.
Verwenden Sie ein Peripheriegerät mit einem kompatiblen seriellen Achtbit-Controllerstrom auf der gewählten D1-Leitung. Die Funktion erkennt das Gerät nicht und implementiert nicht jedes Erweiterungsprotokoll. Jeder Aufruf übernimmt beide Controllerströme neu; beenden Sie einen vollständigen Lesevorgang, bevor Sie den nächsten beginnen. Die Ergebnisse sind neue Momentaufnahmen und aktualisieren weder input_* noch nes_pad1_*.
Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.
Anwendungsbeispiel
d1_second=__pad_read2_d1();Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Dieses Programm vergleicht normale D0-Eingaben mit D1 und dem digitalen Mikrofonpegel. Halten Sie A auf Controller 1 und B auf Controller 2 bis zur Erfassung gedrückt. NORMAL P1 und NORMAL P2 zeigen 001 und 002. Im beigefügten KUROSAKI-Lauf zeigen D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL und MIC DIRECT jeweils 000, weil diese Leitungen nicht emuliert werden. Die erfassten Werte bleiben anschließend stehen. Mit geeigneten Peripheriegeräten und implementierten Signalleitungen hängen die Werte von den tatsächlichen Eingaben ab; deuten Sie sie anhand der obigen Bitbelegung, statt diese Nullen zu erwarten.
Das veröffentlichte KUROSAKI-Controllermodell liefert nur D0 und das feste Bit 6. D1 und das Mikrofonbit sind in diesem Emulator daher stets 0, auch bei gedrückten normalen Tasten. Der beigefügte Lauf prüft Kompilierung, normale Tasteneingaben und diese Nullsignal-Beobachtung. Peripheriesignale ungleich null, elektrische Zeitabläufe und reale Gerätekompatibilität bleiben ungeprüft. Eine Null beweist hier weder, dass das Gerät funktioniert, noch dass die Funktionen es auf echter Hardware nicht lesen können.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\expansion_input.c -I .\kitaq-docs\samples -I .\kitaqfc\lib -o .\out\expansion_input.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:225
Latch and read an eight-button stream from port-2 data line D1 rather than D0.
__pad_read2_safe
Fragt Controller 2 durch wiederholte Paare vollständiger __pad_read2()-Aufrufe ab. Liefert die NES-Maske, sobald beide Werte eines Paares übereinstimmen. Das Ergebnis ist der Haltezustand, keine neue Druckflanke.
Syntax
u8 __pad_read2_safe(void);Rückgabewert
Eine 8-Bit-Haltemaske in roher NES-Reihenfolge. Jedes gesetzte Bit steht für eine gedrückte Taste, 0 für keine. Kein auf 0/1 normalisierter Wahrheitswert.
Bedingungen und Grenzen
Rohes NES-Format: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Ein gesetztes Bit bedeutet gedrückt. Verwende diese Werte mit nes_pad_* und rohen __pad_*-Ergebnissen, nicht die gemeinsamen BTN_*-Masken aus input.h.
Dieser Aufruf liest einen neuen Hardwarezustand. Er aktualisiert weder den gespeicherten input_*- noch den nes_pad1_*-Zustand und erzeugt keine Druck-, Loslass- oder Wiederholungsereignisse. Speichere das Ergebnis und vergleiche es bei Bedarf mit deiner vorherigen Maske.
Die sichere Abfrage wiederholt Paare vollständiger Lesevorgänge, bis beide Masken übereinstimmen. Jeder Lesevorgang strobt $4016 und setzt die serielle Leseposition beider Controller zurück. Die Zahl der Versuche ist unbegrenzt. Diese Prüfung belegt keine Kompatibilität mit sämtlichen DMC-/Peripheriekombinationen.
Anwendungsbeispiel
safe2=__pad_read2_safe(); // The same consistency check on controller 2.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Dieses Programm vergleicht beide Controlleranschlüsse, einfache und auf Übereinstimmung geprüfte Lesevorgänge, Maskenfilter und Kurzmakros. Halten Sie A + Rechts auf Controller 1 und B + Links auf Controller 2 gedrückt. RAW P1, SAFE P1 und ALIAS P1 sind 129; die drei P2-Werte sind 66. BUTTONS P1 ist 1 und DIRS P1 ist 128: Der Richtungsfilter behält die oberen Bitpositionen bei. FAILED CHECKS muss 0 sein. Die erfassten Werte bleiben sichtbar.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\input_raw.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\input_raw.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-disasm --no-cacheImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:211
Repeat pairs of port-2 polls until both bytes match; this does not populate pad.c globals.
__palette_bg_load
Setzt den PPU-Latch zurück, setzt die Adresse 0x3F00 und schreibt genau 16 Quellbytes über PPUDATA für die vier Hintergrundpaletten. Die Übertragung erfolgt sofort; weder die Palettenauswahl der Nametable-Attribute noch Kachelgrafiken werden geändert.
Syntax
void __palette_bg_load(const u8* pal16);Parameter
pal16CPU-Zeiger auf 16 lesbare NES-Palettencode-Bytes, keine RGB-Tripel. Halten Sie die Quellbank bis zur synchronen Rückkehr eingeblendet; Bankwechsel und Längenprüfung finden nicht statt. Verwenden Sie PPU-Schrittweite 1, um die vorgesehenen 16 Palettenadressen zu erreichen.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Die PPU-Adressen 0x3F10/14/18/1C sind Aliase von 0x3F00/04/08/0C. Das Laden der Spritepalette beschreibt daher auch diese Hintergrundeinträge. Der Compiler sendet die Bytes unverändert und prüft keine Farben, mischt keine Paletten und erhält keine gemeinsam genutzten Einträge.
Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.
Anwendungsbeispiel
__palette_bg_load(scene_palette); // Four background palettes, 16 bytes total.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:138
Stream exactly 16 source bytes to 3F00. Arrange PPU access timing and increment one.
kitaqfc/kitaqfc/CodeGenerator.cs:4827
case "__palette_bg_load": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2}, funcName);
void EmitHelperPalette(string name, int ppuAddress)
{
EmitHelperStart(name);
EmitAsm("LDA", Mem(0x2002));
EmitAsm("LDA", Imm((ppuAddress >> 8) & 0xFF));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Imm(ppuAddress & 0xFF));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDY", Imm(0));
string loop = NewGeneratedLabel("pal_loop");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("STA", Mem(0x2007));
EmitAsm("INY");
EmitAsm("CPY", Imm(16));
EmitAsm("BNE", Rel(loop));
EmitAsm("RTS");
}
EmitHelperPalette("__palette_bg_load", 0x3F00);__palette_sp_load
Setzt den PPU-Latch zurück, setzt die Adresse 0x3F10 und schreibt genau 16 Quellbytes über PPUDATA für die vier Spritepaletten. Die Sprite-Attributbits wählen die angezeigte Palette. Der Aufruf verändert weder OAM noch überträgt er Sprite-Kachelgrafiken.
Syntax
void __palette_sp_load(const u8* pal16);Parameter
pal16CPU-Zeiger auf 16 lesbare NES-Palettencode-Bytes, keine RGB-Tripel. Halten Sie die Quellbank bis zur synchronen Rückkehr eingeblendet; Bankwechsel und Längenprüfung finden nicht statt. Verwenden Sie PPU-Schrittweite 1, um die vorgesehenen 16 Palettenadressen zu erreichen.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Die PPU-Adressen 0x3F10/14/18/1C sind Aliase von 0x3F00/04/08/0C. Das Laden der Spritepalette beschreibt daher auch diese Hintergrundeinträge. Der Compiler sendet die Bytes unverändert und prüft keine Farben, mischt keine Paletten und erhält keine gemeinsam genutzten Einträge.
Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.
Anwendungsbeispiel
__palette_sp_load(scene_palette); // Four sprite palettes, 16 bytes total.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:140
Stream exactly 16 source bytes to 3F10; hardware palette aliases also affect background entries.
kitaqfc/kitaqfc/CodeGenerator.cs:4828
case "__palette_sp_load": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2}, funcName);
// Clear queued length and readiness; the overflow indicator has a separate reset intrinsic.
void EmitHelperPalette(string name, int ppuAddress)
{
EmitHelperStart(name);
EmitAsm("LDA", Mem(0x2002));
EmitAsm("LDA", Imm((ppuAddress >> 8) & 0xFF));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Imm(ppuAddress & 0xFF));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDY", Imm(0));
string loop = NewGeneratedLabel("pal_loop");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LDA", IndY(CallArgBase));
EmitAsm("STA", Mem(0x2007));
EmitAsm("INY");
EmitAsm("CPY", Imm(16));
EmitAsm("BNE", Rel(loop));
EmitAsm("RTS");
}
EmitHelperPalette("__palette_sp_load", 0x3F10);__ppu_addr
Wertet ppu_addr einmal aus, liest PPUSTATUS zum Zurücksetzen des gemeinsamen Schreib-Latches und schreibt dann das obere und untere Adressbyte nach PPUADDR. Legt das Ziel späterer PPUDATA-Zugriffe fest, ohne selbst Daten zu schreiben. Adressmaskierung und Spiegelung übernimmt die PPU.
Syntax
void __ppu_addr(u16 ppu_addr);Parameter
ppu_addrPPU-Byteadresse (
0x0000–0x3FFF), kein CPU-Zeiger. Das Ziel muss beschreibbar sein: Musteränderungen benötigen CHR-RAM statt CHR-ROM. PPU-Adressabbildung und Spiegelung gelten weiterhin. Wählen Sie für fortlaufendes Kopieren und Füllen die Schrittweite 1 in PPUCTRL. Führen Sie Befehle zu einem sicheren PPU-Zeitpunkt aus und stellen Sie danach den Scrollzustand her; der Ausführer schreibt PPUADDR und PPUDATA, ohne Scrolleinstellungen wiederherzustellen.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.
Anwendungsbeispiel
address_port=0x21;
__ppu_addr(0x2192); // Reset latch and set the cell at tile (18,12).Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:106
Set the PPU address through the compiler helper; address writes affect the shared scroll/address latch.
kitaqfc/kitaqfc/CodeGenerator.cs:4698
case "__ppu_addr":
if (!Need(1)) return true;
PpuAddr(args[0]); return true;
void PpuAddr(Expr e)
{
int[] t = AcquireTemps(2);
try
{
EmitLoadValue(e, ctx, 2);
EmitAsm("STA", Mem(t[0]));
EmitAsm("STX", Mem(t[1]));
EmitAsm("LDA", Mem(0x2002));
EmitAsm("LDA", Mem(t[1]));
EmitAsm("STA", Mem(0x2006));
EmitAsm("LDA", Mem(t[0]));
EmitAsm("STA", Mem(0x2006));
}
finally { ReleaseTemps(t); }
}__ppu_ctrl_get
Liefert die Softwarekopie von PPUCTRL. Damit können Sie NMI-Aktivierung, Mustertabellenauswahl und Adressschritt vor einer vorübergehenden Änderung sichern.
Syntax
u8 __ppu_ctrl_get(void);Rückgabewert
Liefert den letzten von Bibliothek oder intrinsischen Setzfunktionen gespeicherten Steuerwert als u8. PPUSTATUS wird nicht gelesen.
Bedingungen und Grenzen
PPUCTRL und PPUMASK sind nur beschreibbar. Diese Lesefunktion greift nicht auf die Hardware zu. Direkte Schreibzugriffe auf $2000/$2001 oder eigener Assemblercode umgehen die Softwarekopien. Verwenden Sie konsequent die entsprechenden __ppu_*_set-Operationen. Das Lesen der Kopie verändert weder Latch noch Interruptzustand.
Anwendungsbeispiel
__ppu_ctrl_set(4);
if(__ppu_ctrl_get()!=4)failures=1; // Inspect the software shadow, not PPUSTATUS.
__ppu_ctrl_set(0);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Die ausführbare Alternative bereitet den Bildschirm bei ausgeschalteter Bildausgabe vor, setzt Paletten, löscht Kacheln und Attribute und zeichnet das Testbild. Bei (0,16) beginnt ein blaues Muster aus vollen Kacheln, linken Halbflächen und Dreiecken, bei (0,96) ein grüner Vollblock. Jeder Bereich ist 256×64 Pixel groß; die letzte 8×8-Zelle bei (248,72) beziehungsweise (248,152) bleibt wegen der Übertragung von 255 Kachelbytes schwarz. Bei y=208 stehen ein rotes 8×8-Quadrat an x=16, ein linker 4×8-Balken an x=224 und ein achtzeiliges Dreieck an x=240. FAILED CHECKS zeigt 0. Separate Zustandsprüfungen bestätigen beide Steuerregister, die 32 Palettenbytes und alle 1.024 gelöschten Nametable-Bytes. Die vier deklarierten Aufrufe müssen beim Linken scheitern.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_implemented.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\ppu_implemented.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:103
Read the software PPUCTRL/PPUMASK shadows; use the setters for coherent state. Raw writes to $2000/$2001 bypass these shadows. No PPU hardware read occurs.
kitaqfc/kitaqfc/CodeGenerator.cs:4691
case "__ppu_ctrl_get":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(_runtimePpuCtrlShadowAddress)); return true;__ppu_ctrl_set
Ersetzt PPUCTRL und dessen Softwarekopie durch das übergebene Byte. Steuert NMI-Freigabe, PPU-Adressschrittweite, Mustertabellen, Spritegröße und Basis-Nametable. PPUMASK wird nicht geändert; nicht gesetzte Bits werden nicht aus dem bisherigen Wert übernommen.
Syntax
void __ppu_ctrl_set(u8 value);Parameter
valueVorzeichenloses Byte
0..255, das unverändert geschrieben wird. Gemeint ist ein vollständiges Register- oder Datenbyte, kein Wahrheitswert und keine Bitnummer.
Rückgabewert
Keiner (void).
Anwendungsbeispiel
__ppu_ctrl_set(0x80); // NMI enabled, increment 1, nametable/pattern tables 0.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:100
Replace PPUCTRL, including NMI enable, pattern-table choices and address increment mode.
kitaqfc/kitaqfc/CodeGenerator.cs:4687
case "__ppu_ctrl_set":
if (!Need(1)) return true;
Store1(args[0], _runtimePpuCtrlShadowAddress); EmitAsm("STA", Mem(0x2000)); return true;
// Read software state: the corresponding hardware registers are write-only.__ppu_data
Schreibt value einmal über PPUDATA an die aktuelle PPU-Adresse. Anschließend erhöht die PPU diese je nach PPUCTRL um 1 oder 32. Der Aufruf setzt keine Adresse, setzt keinen Latch zurück und legt keinen Übertragungsauftrag an.
Syntax
void __ppu_data(u8 value);Parameter
valueVorzeichenloses Byte
0..255, das unverändert geschrieben wird. Gemeint ist ein vollständiges Register- oder Datenbyte, kein Wahrheitswert und keine Bitnummer.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.
Anwendungsbeispiel
__ppu_data(3); // Green triangle at (144,96); destination advances by one.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:108
Write one PPUDATA byte using the current PPU address and increment mode.
kitaqfc/kitaqfc/CodeGenerator.cs:4701
case "__ppu_data":
if (!Need(1)) return true;
Load1(args[0]); EmitAsm("STA", Mem(0x2007)); return true;
// Reading PPUSTATUS also has hardware side effects; this is not a side-effect-free status cache.__ppu_mask_get
Liefert die Softwarekopie von PPUMASK. Damit bleiben Einstellungen für Darstellung, Sichtbarkeit am linken Rand, Graustufen und Farbbetonung erhalten.
Syntax
u8 __ppu_mask_get(void);Rückgabewert
Liefert den letzten von Bibliothek oder intrinsischen Setzfunktionen gespeicherten Steuerwert als u8. PPUSTATUS wird nicht gelesen.
Bedingungen und Grenzen
PPUCTRL und PPUMASK sind nur beschreibbar. Diese Lesefunktion greift nicht auf die Hardware zu. Direkte Schreibzugriffe auf $2000/$2001 oder eigener Assemblercode umgehen die Softwarekopien. Verwenden Sie konsequent die entsprechenden __ppu_*_set-Operationen. Das Lesen der Kopie verändert weder Latch noch Interruptzustand.
Anwendungsbeispiel
__ppu_mask_set(0xE7);
if(__ppu_mask_get()!=0xE7)failures=1;
__ppu_mask_set(0);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Die ausführbare Alternative bereitet den Bildschirm bei ausgeschalteter Bildausgabe vor, setzt Paletten, löscht Kacheln und Attribute und zeichnet das Testbild. Bei (0,16) beginnt ein blaues Muster aus vollen Kacheln, linken Halbflächen und Dreiecken, bei (0,96) ein grüner Vollblock. Jeder Bereich ist 256×64 Pixel groß; die letzte 8×8-Zelle bei (248,72) beziehungsweise (248,152) bleibt wegen der Übertragung von 255 Kachelbytes schwarz. Bei y=208 stehen ein rotes 8×8-Quadrat an x=16, ein linker 4×8-Balken an x=224 und ein achtzeiliges Dreieck an x=240. FAILED CHECKS zeigt 0. Separate Zustandsprüfungen bestätigen beide Steuerregister, die 32 Palettenbytes und alle 1.024 gelöschten Nametable-Bytes. Die vier deklarierten Aufrufe müssen beim Linken scheitern.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_implemented.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\ppu_implemented.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:104
kitaqfc/kitaqfc/CodeGenerator.cs:4694
case "__ppu_mask_get":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(_runtimePpuMaskShadowAddress)); return true;
// Direct PPU address/data access does not wait for a safe rendering interval here.__ppu_mask_set
Ersetzt das gesamte PPUMASK-Byte und seine Softwarekopie durch value. Wählen Sie Bildausgabe, linken Rand, Graustufen und Farbbetonung ausdrücklich aus. Die Funktion kombiniert keine Bits mit der vorherigen Maske und verändert PPUCTRL nicht.
Syntax
void __ppu_mask_set(u8 value);Parameter
valueVorzeichenloses Byte
0..255, das unverändert geschrieben wird. Gemeint ist ein vollständiges Register- oder Datenbyte, kein Wahrheitswert und keine Bitnummer.
Rückgabewert
Keiner (void).
Anwendungsbeispiel
__ppu_mask_set(0x0A); // Background enabled, including its leftmost eight pixels.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:98
Replace PPUMASK with the supplied byte; the caller chooses rendering, clipping and emphasis bits.
kitaqfc/kitaqfc/CodeGenerator.cs:4683
case "__ppu_mask_set":
if (!Need(1)) return true;
Store1(args[0], _runtimePpuMaskShadowAddress); EmitAsm("STA", Mem(0x2001)); return true;
// Update the PPUCTRL shadow before writing the hardware register.__ppu_off
Schreibt null nach PPUMASK und in dessen Softwarekopie und schaltet Hintergrund und Sprites aus. Alle Maskenbits einschließlich Graustufen und Farbbetonung werden gelöscht. PPUCTRL und die NMI-Freigabe bleiben unverändert; steuern Sie NMI bei der Vorbereitung gemeinsam genutzter Übertragungsdaten separat.
Syntax
void __ppu_off(void);Rückgabewert
Keiner (void).
Anwendungsbeispiel
__ppu_off(); // PPUMASK and its software shadow become zero; NMI is unchanged.
__ppu_ctrl_set(0); // Disable NMI separately while preparing the whole scene.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:96
Invoke the compiler rendering-disable helper before bulk direct PPU updates.
kitaqfc/kitaqfc/CodeGenerator.cs:4679
case "__ppu_off":
if (!Need(0)) return true;
EmitAsm("LDA", Imm(0)); EmitAsm("STA", Mem(_runtimePpuMaskShadowAddress)); EmitAsm("STA", Mem(0x2001)); return true;
// Keep the supplied mask available for later runtime code that restores PPU state.__ppu_on
Ersetzt PPUMASK und dessen Softwarekopie durch 0x1E und aktiviert Hintergrund und Sprites einschließlich der linken acht Pixel. Graustufen- und Farbbetonungseinstellungen werden überschrieben; PPUCTRL und die NMI-Freigabe werden nicht geändert.
Syntax
void __ppu_on(void);Rückgabewert
Keiner (void).
Anwendungsbeispiel
__ppu_on(); // Replace mask with 0x1E: enable sprites and background at both edges.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:94
PRG bank Resolve a symbol or function placement to its byte bank number at compilation time. u8 __bankof(u16 symbol_or_function); Select the mapper-specific PRG bank; the common bank and legal bank range depend on the target board. void __bankswitch(u8 bank); Use the same mapper PRG-switch helper as __bankswitch; retain executable code in a valid mapping. void __prg_bank_set(u8 bank); Call a declared zero-argument function; placement selects its bank and its declared type determines the result. u8 __farcall(u8 bank, u16 func); Switch to the source bank, copy len bytes forward and restore the previous bank. Keep destination RAM and helper code accessible throughout; overlapping copies are not memmove. void __far_memcpy(u8* dst, u8 bank, u16 src, u16 len); Temporarily select bank, read one byte at addr and restore the prior bank mapping. u8 __farpeek8(u8 bank, u16 addr); Temporarily select bank and read a little-endian word, then restore the previous mapping. u16 __farpeek16(u8 bank, u16 addr); PPU / VRAM Invoke the compiler rendering-enable helper; this header contains declarations, not C fallback implementations.
kitaqfc/kitaqfc/CodeGenerator.cs:4676
case "__ppu_on":
if (!Need(0)) return true;
EmitAsm("LDA", Imm(0x1E)); EmitAsm("STA", Mem(_runtimePpuMaskShadowAddress)); EmitAsm("STA", Mem(0x2001)); return true;__ppu_read_status
Liest PPUSTATUS einmal und gibt das Byte zurück, das vor dem durch den Lesezugriff ausgelösten Löschen des VBlank-Flags beobachtet wurde. Setzt außerdem den gemeinsamen Schreib-Latch für Bildlauf und Adresse zurück. Dies ist ein Hardware-Statuszugriff mit Nebenwirkungen, kein gespeicherter Bildzähler und keine NMI-Abschlussprüfung.
Syntax
u8 __ppu_read_status(void);Rückgabewert
Rohes Statusbyte. Bit 7 meldet VBlank, Bit 6 Sprite-Zero-Hit und Bit 5 Sprite-Überlauf. Prüfen Sie das benötigte Flag mit einer Maske; behandeln Sie das gesamte Byte nicht als Wahrheitswert oder Bildnummer. Die unteren Bits sind kein portabler Anwendungsstatus.
Bedingungen und Grenzen
Ein PPUSTATUS-Lesezugriff kann das NMI-Timing und die nächsten Schreibzugriffe auf PPUSCROLL und PPUADDR beeinflussen. Fügen Sie ihn nicht zwischen ein erst halb geschriebenes Registerpaar einer anderen Routine ein. Diese Aufrufe warten nicht auf VBlank und arbeiten keine ausstehenden Übertragungsaufträge ab.
Anwendungsbeispiel
address_port=0x21;
status_snapshot=__ppu_read_status(); // Also resets the shared write latch.
address_port=0x21;address_port=0x84;data_port=3; // Blue triangle at (32,96).Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:110
Read PPUSTATUS; the hardware clears VBlank status and resets the shared write latch.
kitaqfc/kitaqfc/CodeGenerator.cs:4705
case "__ppu_read_status":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(0x2002)); return true;__prg_bank_set
Verwendet dieselbe Mapper-gerechte PRG-Bankauswahl wie __bankswitch. Es gelten dieselben Bankprüfungen; die vorherige Zuordnung wird nicht automatisch wiederhergestellt.
Syntax
void __prg_bank_set(u8 bank);Parameter
bankVorzeichenlose 8-Bit-ROM-Banknummer. Bei Aufrufen muss sie zur Funktionsplatzierung passen, bei Lesezugriffen wählt sie die Quelle. Verwenden Sie den gültigen Bereich des Modultyps.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Ein dauerhafter Bankwechsel darf weder den laufenden Code noch seine Rückkehradresse ausblenden. Legen Sie den Aufruf, gegebenenfalls bank_switch selbst und den anschließend ausgeführten Code wie im Beispiel in die gemeinsame beziehungsweise feste Bank 0.
Der Bibliothekszustand beginnt bei null und erkennt die Startzuordnung des Moduls nicht. Initialisieren Sie ihn mit bank_switch. Direkte Intrinsics und Mapper-Schreibzugriffe aktualisieren diesen separaten Zustand nicht; bank_get_current kann die tatsächliche Hardwarezuordnung daher nicht prüfen.
FC-Banknummern sind logische Nummern des Compilers. Null bezeichnet gemeinsames ROM, kein Umschaltziel; die Hilfsroutine setzt null auf Bank 1 um. Bei SUROM512 sind nur 1–30 gültig: Eine direkt an das Umschalt-Intrinsic übergebene Konstante außerhalb des Bereichs führt zu einem Buildfehler; ein ungültiger Laufzeitwert wählt 1 und setzt den Mapper-Fehlerzustand. Lesen Sie gemeinsames ROM direkt, statt Bank null anzufordern.
Anwendungsbeispiel
__prg_bank_set(1);bank_expect(home1[0]==0xA1,21);
__prg_bank_set(2); // Same mapper helper as __bankswitch.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Der anfängliche Aufruf bank_switch(2) speichert und wählt Bank 2. Ein späterer direkter Wechsel wählt Bank 1 und liest A1 aus home1, während bank_get_current weiterhin 2 meldet. Nach dem Zurückschalten liefert home2 wieder B2.
Das vollständige Programm zeigt Datenzugriffe über Bank und Nahadresse, bankübergreifende Callbacks und den Unterschied zwischen Bibliothekszustand und tatsächlicher Zuordnung. Alle Zahlen sind hexadezimal. BANK IN und BANK OUT müssen 02 zeigen, BYTE HEX D3, WORD HEX 5C7A sowie FAILED CHECKS und FIRST FAILURE jeweils 00. CGB und FC zeigen blaue Schrift, DMG schwarze.
CALL COUNT zeigt 02: Das Makro far_call und __farcall rufen den Callback jeweils einmal auf.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\banked_data_calls.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\banked_data_calls.nes --mapper=mmc3 --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:81
Use the same mapper PRG-switch helper as __bankswitch; retain executable code in a valid mapping.
kitaqfc/kitaqfc/CodeGenerator.cs:4746
case "__prg_bank_set":
case "__bankswitch":
if (!Need(1)) return true;
if (Program.NesMapperProfile.IsSurom512 && TryEvaluateConstant(args[0], out int constantBank) && (constantBank < 1 || constantBank > 30))
{
Program.Error("error KQFC2603 KQFC-SUROM-BANK-RANGE: {0} constant bank {1} is outside 1..30; common bank 0 is not a switch target.", funcName, constantBank);
return true;
}
_mapperWritingFunctions.Add(_currentFunctionName);
Load1(args[0]); EmitAsm("JSR", Abs("__kq_prg_set_bank_a")); return true;__rng8
Schaltet das gemeinsame 16-Bit-Galois-LFSR des FC einmal weiter und liefert das XOR der aktualisierten Low- und High-Bytes. Ein LFSR ist ein Schieberegister mit per XOR berechneter Rückkopplung; ein fester Startwert erzeugt daher eine reproduzierbare Bytefolge.
Syntax
u8 __rng8(void);Rückgabewert
Ein u8-Ergebnis (0–255) nach einmaligem Fortschalten des gemeinsamen Generators. Dies beschreibt Typ und Grenzen, nicht gleiche Wahrscheinlichkeit für jedes Byte.
Bedingungen und Grenzen
Der FC-Schritt schiebt den 16-Bit-Zustand um ein Bit nach rechts. War das ursprüngliche niederwertigste Bit 1, wird der geschobene Zustand per XOR mit 0xB400 verknüpft. Zurückgegeben wird low_byte XOR high_byte, nicht das rohe Low-Byte.
Sind beide Zustandsbytes null, setzt die nächste Ziehung vor dem Schieben 0xA55A ein. Daher liefert __rng_seed(0) gefolgt von __rng8() den Wert 255. Der Startwertaufruf speichert null; der Ersatz erfolgt erst beim Ziehen.
Diese Aufrufe teilen einen persistenten Zustand. Reproduktion erfordert dasselbe Zielsystem, denselben Startwert und dieselbe Aufrufreihenfolge einschließlich anderer Hilfsfunktionen. Führen Sie Startwertsetzung und Ziehungen in einem Ausführungskontext aus; die Prüfungen belegen keine sichere gleichzeitige Nutzung aus einem Interrupthandler.
GB und FC verwenden unterschiedliche Algorithmen und versprechen bei gleichem Startwert keine gleiche Folge. Dies sind deterministische Spielgeneratoren, keine Quellen kryptografischer Zufälligkeit. Die Tests prüfen Implementierungsverhalten, nicht statistische Qualität oder Periode.
Anwendungsbeispiel
__rng_seed(0x1234);
first=__rng8();second=__rng8();third=__rng8(); // FC sequence: 19, 137, 240.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Dieses Beispiel liest drei aufeinanderfolgende FC-Bytes. Startwert 0x1234 erzeugt FIRST 00019, SECOND 00137 und THIRD 00240. Diese Werte zeigen die Aufrufreihenfolge, keine Häufigkeit oder Zufälligkeitsbewertung.
Das vollständige NROM-Programm zeigt blaue Dezimalergebnisse auf Schwarz. FAILED muss 00000 anzeigen. Der Text wird bei deaktivierter Darstellung übertragen, bevor der Ergebnisbildschirm eingeschaltet wird.
Die Prüfung verglich je Fall 32 Ausgaben und acht anschließende Byteziehungen mit einem unabhängigen Hostmodell: 93 GB- und 18 FC-Fälle, einschließlich konstanter und zur Laufzeit gesetzter Startwerte sowie GB-Grenzfällen für Bereiche, Chancen und Gewichte. Die Lehrbildschirme wurden nach 240 Emulatorframes geprüft. Echte Hardware und statistische Qualität wurden nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\rng_core.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\rng_core.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:39
Advance the shared 16-bit LFSR and return the XOR of its two bytes; zero state is reseeded to A55A.
kitaqfc/kitaqfc/CodeGenerator.cs:4663
case "__rng8":
if (!Need(0)) return true;
EmitAsm("JSR", Abs("__rng8"));
return true;
// Far-memory helpers pack the address, bank and length arguments according to their explicit byte/word signatures.
EmitHelperStart("__rng8");
// 16-bit Galois LFSR: if low bit set, shift right then xor $B400.
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("ORA", Mem(_runtimeRngHiAddress));
// Replace the all-zero LFSR state with a deterministic nonzero seed before advancing.
string seeded = NewGeneratedLabel("rng_seeded");
EmitAsm("BNE", Rel(seeded));
EmitAsm("LDA", Imm(0x5A));
EmitAsm("STA", Mem(_runtimeRngLoAddress));
EmitAsm("LDA", Imm(0xA5));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
_assembly.Add(Expr.Make(Tag.Label, seeded));
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("AND", Imm(1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LSR", Mem(_runtimeRngHiAddress));
EmitAsm("ROR", Mem(_runtimeRngLoAddress));
string rngNoXor = NewGeneratedLabel("rng_no_xor");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("BEQ", Rel(rngNoXor));
EmitAsm("LDA", Mem(_runtimeRngHiAddress));
EmitAsm("EOR", Imm(0xB4));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
_assembly.Add(Expr.Make(Tag.Label, rngNoXor));
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("EOR", Mem(_runtimeRngHiAddress));
EmitAsm("RTS");__rng_seed
Ersetzt beide Zustandsbytes des FC-Generators. Derselbe Startwert lässt einen Test oder eine Wiederholung am selben Punkt der Pseudozufallsfolge beginnen; der Aufruf schaltet den Generator nicht weiter.
Syntax
void __rng_seed(u16 seed);Parameter
seedVorzeichenloser 16-Bit-Startwert in 0–65535. Beide Bytes werden unverändert gespeichert. Wählen und notieren Sie den Startwert vor der Folge von Spielziehungen, die Sie reproduzieren möchten.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Sind beide Zustandsbytes null, setzt die nächste Ziehung vor dem Schieben 0xA55A ein. Daher liefert __rng_seed(0) gefolgt von __rng8() den Wert 255. Der Startwertaufruf speichert null; der Ersatz erfolgt erst beim Ziehen.
Diese Aufrufe teilen einen persistenten Zustand. Reproduktion erfordert dasselbe Zielsystem, denselben Startwert und dieselbe Aufrufreihenfolge einschließlich anderer Hilfsfunktionen. Führen Sie Startwertsetzung und Ziehungen in einem Ausführungskontext aus; die Prüfungen belegen keine sichere gleichzeitige Nutzung aus einem Interrupthandler.
GB und FC verwenden unterschiedliche Algorithmen und versprechen bei gleichem Startwert keine gleiche Folge. Dies sind deterministische Spielgeneratoren, keine Quellen kryptografischer Zufälligkeit. Die Tests prüfen Implementierungsverhalten, nicht statistische Qualität oder Periode.
Anwendungsbeispiel
__rng_seed(0x1234);
repeated=__rng8(); // The first byte is 19 again.
__rng_seed(0);
zero=__rng8(); // The next draw uses the FC fallback state and returns 255.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Dieses Beispiel zeigt reproduzierbaren FC-Start und die Nullstartwertregel. FIRST und RESEEDED zeigen bei Startwert 0x1234 jeweils 00019. ZERO SEED zeigt nach Startwert null den Wert 00255.
Das vollständige NROM-Programm zeigt blaue Dezimalergebnisse auf Schwarz. FAILED muss 00000 anzeigen. Der Text wird bei deaktivierter Darstellung übertragen, bevor der Ergebnisbildschirm eingeschaltet wird.
Die Prüfung verglich je Fall 32 Ausgaben und acht anschließende Byteziehungen mit einem unabhängigen Hostmodell: 93 GB- und 18 FC-Fälle, einschließlich konstanter und zur Laufzeit gesetzter Startwerte sowie GB-Grenzfällen für Bereiche, Chancen und Gewichte. Die Lehrbildschirme wurden nach 240 Emulatorframes geprüft. Echte Hardware und statistische Qualität wurden nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\rng_core.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\rng_core.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:37
Replace both persistent state bytes. Reusing a seed reproduces the sequence; this is not cryptographic randomness.
kitaqfc/kitaqfc/CodeGenerator.cs:4661
case "__rng_seed":
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2}, "__rng_seed");
EmitHelperStart("__rng_seed");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeRngLoAddress));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
EmitAsm("RTS");__rob_flash
Wählt die Ein- oder Ausdarstellungsmaske und wartet auf eine Änderung des NMI-Zählers. Die gewählte Maske bleibt nach Rückkehr wirksam.
Syntax
void __rob_flash(u8 bright);Parameter
brightNull wählt aus, jedes Byte ungleich null einschließlich 255 wählt an. Dies ist keine Helligkeitsstufe.
Rückgabewert
Kein Rückgabewert. Kehrt nach Abschluss der angeforderten Wartezeiten zum Aufrufer zurück.
Bedingungen und Grenzen
Binden Sie intrinsics.h ein. Der Standard-NMI-Handler muss laufen und seinen Zähler erhöhen. Bei deaktiviertem NMI oder einem eigenen Handler ohne diese Zählerpflege bleibt die Wartefunktion blockiert. Es gibt kein Zeitlimit.
Die Einphase setzt PPUMASK und seine Softwarekopie auf 0x1E, die Ausphase beide auf null. Die vorherige Maske wird nicht wiederhergestellt. Dies ändert Hintergrund-, Sprite- und linke Randdarstellung gemeinsam und ersetzt auch Graustufen- und Farbbetonungseinstellungen.
Das Einschalten der Darstellung erzeugt allein keinen weißen Bildschirm. Stellen Sie helle Kacheln und Paletteneinträge sowie eine dunkle Hintergrundfarbe für die Ausphase bereit. Das Beispiel nutzt vollständig weiße Kacheln und schwarzen Hintergrund, um während des Vorgangs den ganzen Bildschirm blinken zu lassen.
Wartezählungen messen Änderungen des NMI-Zählers. Die Zeit vom Eintritt bis zum ersten NMI kann kürzer als ein Frame sein. Rufen Sie aus dem Hauptcode auf, nicht aus einem Interrupt. Andere Routinen, die die Darstellungsmaske ändern, können das Muster stören.
Dies sind Grundoperationen für Anzeigemaske und Warten. Sie erzeugen weder einen Roboterbefehlsvorspann noch gerätespezifische Befehlsfolgen oder Empfangsbestätigungen. Geprüft wurden PPUMASK-Schreibzugriffe, NMI-Zählungen und Pixel in KUROSAKI; der Empfang eines echten Roboters wurde nicht getestet.
Anwendungsbeispiel
start=__nmi_ready();
__rob_flash(1); // Enable rendering and wait for one NMI-counter change.
rob_result[0]=(u8)(__nmi_ready()-start);
__rob_flash(0); // Disable rendering; the black backdrop remains visible.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Nach Ausführung zeigt der Bildschirm FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 und A5 BYTE WAITS 48. Das Diagramm stellt 0xA5 dar: oben 1,0,1,0, unten 0,1,0,1. Jede Zelle entspricht einer Warteoperation; Weiß bedeutet Darstellung an, Schwarz aus. Eine Eins hat vier weiße und zwei schwarze Zellen, eine Null zwei weiße und vier schwarze. FINAL MASK 0 und TIMING CHECKS OK zeigen den Abschluss.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\rob_timing.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\rob_timing.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:272
Set PPUMASK and its shadow to 1E or 00, then wait for an NMI-counter change. The caller supplies image/palette brightness; rendering enable alone does not create a white screen.
kitaqfc/kitaqfc/CodeGenerator.cs:7867
EmitHelperStart("__rob_flash");
EmitAsm("LDA", Mem(CallArgBase));
string black = NewGeneratedLabel("rob_black");
EmitAsm("BEQ", Rel(black));
EmitAsm("LDA", Imm(0x1E));
EmitAsm("STA", Mem(_runtimePpuMaskShadowAddress));
EmitAsm("STA", Mem(0x2001));
EmitAsm("JSR", Abs("__nmi_wait"));
EmitAsm("RTS");
_assembly.Add(Expr.Make(Tag.Label, black));
EmitAsm("LDA", Imm(0x00));
EmitAsm("STA", Mem(_runtimePpuMaskShadowAddress));
EmitAsm("STA", Mem(0x2001));
EmitAsm("JSR", Abs("__nmi_wait"));
EmitAsm("RTS");
// Emit the requested number of enabled frames followed by the requested disabled frames.__rob_pulse
Führt die angeforderten Einwartephasen und danach die Auswartephasen aus und erzeugt damit einen Puls.
Syntax
void __rob_pulse(u8 on_frames, u8 off_frames);Parameter
on_framesWarteanzahl der Einphase in 0..255. Null überspringt diese Phase.
off_framesWarteanzahl der Ausphase in 0..255. Null überspringt diese Phase. Insgesamt sind bis zu 510 Warteoperationen möglich.
Rückgabewert
Kein Rückgabewert. Kehrt nach Abschluss der angeforderten Wartezeiten zum Aufrufer zurück.
Bedingungen und Grenzen
Sind beide Anzahlen null, ändern sich weder Maske noch Timing. Jede Auswartephase hinterlässt Maske null; ein reiner Einpuls hinterlässt 0x1E.
Binden Sie intrinsics.h ein. Der Standard-NMI-Handler muss laufen und seinen Zähler erhöhen. Bei deaktiviertem NMI oder einem eigenen Handler ohne diese Zählerpflege bleibt die Wartefunktion blockiert. Es gibt kein Zeitlimit.
Die Einphase setzt PPUMASK und seine Softwarekopie auf 0x1E, die Ausphase beide auf null. Die vorherige Maske wird nicht wiederhergestellt. Dies ändert Hintergrund-, Sprite- und linke Randdarstellung gemeinsam und ersetzt auch Graustufen- und Farbbetonungseinstellungen.
Das Einschalten der Darstellung erzeugt allein keinen weißen Bildschirm. Stellen Sie helle Kacheln und Paletteneinträge sowie eine dunkle Hintergrundfarbe für die Ausphase bereit. Das Beispiel nutzt vollständig weiße Kacheln und schwarzen Hintergrund, um während des Vorgangs den ganzen Bildschirm blinken zu lassen.
Wartezählungen messen Änderungen des NMI-Zählers. Die Zeit vom Eintritt bis zum ersten NMI kann kürzer als ein Frame sein. Rufen Sie aus dem Hauptcode auf, nicht aus einem Interrupt. Andere Routinen, die die Darstellungsmaske ändern, können das Muster stören.
Dies sind Grundoperationen für Anzeigemaske und Warten. Sie erzeugen weder einen Roboterbefehlsvorspann noch gerätespezifische Befehlsfolgen oder Empfangsbestätigungen. Geprüft wurden PPUMASK-Schreibzugriffe, NMI-Zählungen und Pixel in KUROSAKI; der Empfang eines echten Roboters wurde nicht getestet.
Anwendungsbeispiel
start=__nmi_ready();
__rob_pulse(3,2); // Three enabled waits, then two disabled waits.
rob_result[1]=(u8)(__nmi_ready()-start);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Nach Ausführung zeigt der Bildschirm FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 und A5 BYTE WAITS 48. Das Diagramm stellt 0xA5 dar: oben 1,0,1,0, unten 0,1,0,1. Jede Zelle entspricht einer Warteoperation; Weiß bedeutet Darstellung an, Schwarz aus. Eine Eins hat vier weiße und zwei schwarze Zellen, eine Null zwei weiße und vier schwarze. FINAL MASK 0 und TIMING CHECKS OK zeigen den Abschluss.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\rob_timing.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\rob_timing.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:274
Run on_frames enabled-mask waits followed by off_frames disabled-mask waits; NMI must advance.
kitaqfc/kitaqfc/CodeGenerator.cs:7884
EmitHelperStart("__rob_pulse");
EmitAsm("LDX", Mem(CallArgBase));
string onLoop = NewGeneratedLabel("rob_on_loop");
string offStart = NewGeneratedLabel("rob_off_start");
_assembly.Add(Expr.Make(Tag.Label, onLoop));
EmitAsm("CPX", Imm(0));
EmitAsm("BEQ", Rel(offStart));
EmitAsm("LDA", Imm(1));
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__rob_flash"));
EmitAsm("DEX");
EmitAsm("BNE", Rel(onLoop));
_assembly.Add(Expr.Make(Tag.Label, offStart));
EmitAsm("LDX", Mem(CallArgBase + 1));
string offLoop = NewGeneratedLabel("rob_off_loop");
string pulseDone = NewGeneratedLabel("rob_pulse_done");
_assembly.Add(Expr.Make(Tag.Label, offLoop));
EmitAsm("CPX", Imm(0));
EmitAsm("BEQ", Rel(pulseDone));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("JSR", Abs("__rob_flash"));
EmitAsm("DEX");
EmitAsm("BNE", Rel(offLoop));
_assembly.Add(Expr.Make(Tag.Label, pulseDone));
EmitAsm("RTS");
// Choose a four/two or two/four frame pulse pattern for each current high bit.__rob_send_byte
Codiert ein Byte als acht Blinkmuster, beginnend mit dem höchstwertigen Bit. Eins nutzt vier Ein- und zwei Auswartephasen, null zwei Ein- und vier Auswartephasen. Insgesamt sind es 48 NMI-Warteoperationen.
Syntax
void __rob_send_byte(u8 pattern);Parameter
patternAchtbitmuster in 0..255. Für 0xA5 gilt die Reihenfolge 1,0,1,0,0,1,0,1. Dies ist weder Kanal-ID noch Roboteraktionsnummer.
Rückgabewert
Kein Rückgabewert. Kehrt nach Abschluss der angeforderten Wartezeiten zum Aufrufer zurück.
Bedingungen und Grenzen
Auch das letzte Bit endet mit Auswartephasen und lässt PPUMASK samt Kopie auf null. Es werden genau acht Bits gesendet, ohne Start- oder Endmarkierung.
Binden Sie intrinsics.h ein. Der Standard-NMI-Handler muss laufen und seinen Zähler erhöhen. Bei deaktiviertem NMI oder einem eigenen Handler ohne diese Zählerpflege bleibt die Wartefunktion blockiert. Es gibt kein Zeitlimit.
Die Einphase setzt PPUMASK und seine Softwarekopie auf 0x1E, die Ausphase beide auf null. Die vorherige Maske wird nicht wiederhergestellt. Dies ändert Hintergrund-, Sprite- und linke Randdarstellung gemeinsam und ersetzt auch Graustufen- und Farbbetonungseinstellungen.
Das Einschalten der Darstellung erzeugt allein keinen weißen Bildschirm. Stellen Sie helle Kacheln und Paletteneinträge sowie eine dunkle Hintergrundfarbe für die Ausphase bereit. Das Beispiel nutzt vollständig weiße Kacheln und schwarzen Hintergrund, um während des Vorgangs den ganzen Bildschirm blinken zu lassen.
Wartezählungen messen Änderungen des NMI-Zählers. Die Zeit vom Eintritt bis zum ersten NMI kann kürzer als ein Frame sein. Rufen Sie aus dem Hauptcode auf, nicht aus einem Interrupt. Andere Routinen, die die Darstellungsmaske ändern, können das Muster stören.
Dies sind Grundoperationen für Anzeigemaske und Warten. Sie erzeugen weder einen Roboterbefehlsvorspann noch gerätespezifische Befehlsfolgen oder Empfangsbestätigungen. Geprüft wurden PPUMASK-Schreibzugriffe, NMI-Zählungen und Pixel in KUROSAKI; der Empfang eines echten Roboters wurde nicht getestet.
Anwendungsbeispiel
start=__nmi_ready();
__rob_send_byte(0xA5); // 10100101, most-significant bit first; 48 waits.
rob_result[2]=(u8)(__nmi_ready()-start);
rob_result[3]=__ppu_mask_get(); // Zero: the sequence ends with rendering off.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Nach Ausführung zeigt der Bildschirm FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 und A5 BYTE WAITS 48. Das Diagramm stellt 0xA5 dar: oben 1,0,1,0, unten 0,1,0,1. Jede Zelle entspricht einer Warteoperation; Weiß bedeutet Darstellung an, Schwarz aus. Eine Eins hat vier weiße und zwei schwarze Zellen, eine Null zwei weiße und vier schwarze. FINAL MASK 0 und TIMING CHECKS OK zeigen den Abschluss.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\rob_timing.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\rob_timing.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:278
Send eight bits MSB first: one uses 4 on/2 off waits; zero uses 2 on/4 off. Requires an advancing runtime NMI counter; 48 waits in total, final PPUMASK=0. This raw pattern does not add a robot command preamble or validate reception.
kitaqfc/kitaqfc/CodeGenerator.cs:7912
EmitHelperStart("__rob_send_byte");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("LDX", Imm(8));
string bitLoop = NewGeneratedLabel("rob_bit_loop");
string bitZero = NewGeneratedLabel("rob_bit_zero");
string bitNext = NewGeneratedLabel("rob_bit_next");
_assembly.Add(Expr.Make(Tag.Label, bitLoop));
// Preserve the remaining payload and outer count on the CPU stack.
// Pulse consumes X; its NMI wait also uses the shared scratch byte.
// ASL selects the next MSB in carry, which TXA/PHA leave unchanged.
EmitAsm("ASL");
EmitAsm("PHA");
EmitAsm("TXA");
EmitAsm("PHA");
EmitAsm("BCC", Rel(bitZero));
EmitAsm("LDA", Imm(4));
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Imm(2));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("JSR", Abs("__rob_pulse"));
EmitAsm("JMP", Abs(bitNext));
_assembly.Add(Expr.Make(Tag.Label, bitZero));
EmitAsm("LDA", Imm(2));
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Imm(4));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("JSR", Abs("__rob_pulse"));
_assembly.Add(Expr.Make(Tag.Label, bitNext));
EmitAsm("PLA");
EmitAsm("TAX");
EmitAsm("PLA");
EmitAsm("DEX");
EmitAsm("BNE", Rel(bitLoop));
EmitAsm("RTS");
}__scroll_latch_reset
Liest PPUSTATUS und verwirft das Ergebnis, um den gemeinsamen Schreib-Latch für Bildlauf und Adresse zurückzusetzen. Löscht dabei auch das VBlank-Flag. Es werden weder Bildlaufkoordinaten von null geschrieben noch gespeicherte X/Y-Werte verändert.
Syntax
void __scroll_latch_reset(void);Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Ein PPUSTATUS-Lesezugriff kann das NMI-Timing und die nächsten Schreibzugriffe auf PPUSCROLL und PPUADDR beeinflussen. Fügen Sie ihn nicht zwischen ein erst halb geschriebenes Registerpaar einer anderen Routine ein. Diese Aufrufe warten nicht auf VBlank und arbeiten keine ausstehenden Übertragungsaufträge ab.
Anwendungsbeispiel
address_port=0x21; // Leave the shared latch halfway through an address pair.
__scroll_latch_reset();
address_port=0x21;address_port=0x83;data_port=2; // Blue left-half marker.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:112
Read PPUSTATUS to reset the scroll/address write latch; the read also clears VBlank status.
kitaqfc/kitaqfc/CodeGenerator.cs:4708
case "__scroll_latch_reset":
if (!Need(0)) return true;
EmitAsm("LDA", Mem(0x2002)); return true;
// Store both scroll components, reset the write latch, then emit the X/Y pair.__scroll_set
Speichert beide Pixelversätze in den X/Y-Kopien der Laufzeitbibliothek, liest PPUSTATUS zum Zurücksetzen des Latches und schreibt dann X und Y in dieser Reihenfolge nach PPUSCROLL. Aktualisiert den Bildlauf direkt, ohne eine Kamerabewegung vorzumerken, auf NMI zu warten oder eine Basis-Nametable auszuwählen.
Syntax
void __scroll_set(u8 x, u8 y);Parameter
x / yDie Versätze sind vorzeichenlose Pixelbytes. Verwenden Sie X
0..255und normalerweise Y0..239; Y240..255wird unverändert geschrieben, hat aber ein besonderes PPU-Zeilenverhalten statt normalen sichtbaren Kartenbildlaufs. PPUCTRL liefert die Basis-Nametable-Bits. Stellen Sie den Bildlauf nach direktem VRAM-Zugriff vor der Bildausgabe wieder her; die Funktion plant diesen Zeitpunkt nicht.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Ein PPUSTATUS-Lesezugriff kann das NMI-Timing und die nächsten Schreibzugriffe auf PPUSCROLL und PPUADDR beeinflussen. Fügen Sie ihn nicht zwischen ein erst halb geschriebenes Registerpaar einer anderen Routine ein. Diese Aufrufe warten nicht auf VBlank und arbeiten keine ausstehenden Übertragungsaufträge ab.
Anwendungsbeispiel
__scroll_set(19,27); // Save both coordinates and write the two-scroll-byte pair.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:114
Save X/Y in runtime shadows, reset the PPU latch and write both scroll bytes immediately.
kitaqfc/kitaqfc/CodeGenerator.cs:4712
case "__scroll_set":
if (!Need(2)) return true;
Store1(args[0], _runtimeScrollXAddress); Store1(args[1], _runtimeScrollYAddress);
EmitAsm("LDA", Mem(0x2002)); EmitAsm("LDA", Mem(_runtimeScrollXAddress)); EmitAsm("STA", Mem(0x2005)); EmitAsm("LDA", Mem(_runtimeScrollYAddress)); EmitAsm("STA", Mem(0x2005)); return true;
// Changing X rewrites both latch bytes, using the retained Y component.__scroll_x_set
Ersetzt das gespeicherte X, setzt den Latch durch einen Statuslesezugriff zurück und schreibt das neue X sowie das gespeicherte Y nach PPUSCROLL. Y stammt aus der Softwarekopie, nicht aus der PPU; direkte Registerzugriffe aktualisieren diese Kopie nicht.
Syntax
void __scroll_x_set(u8 x);Parameter
xDie Versätze sind vorzeichenlose Pixelbytes. Verwenden Sie X
0..255und normalerweise Y0..239; Y240..255wird unverändert geschrieben, hat aber ein besonderes PPU-Zeilenverhalten statt normalen sichtbaren Kartenbildlaufs. PPUCTRL liefert die Basis-Nametable-Bits. Stellen Sie den Bildlauf nach direktem VRAM-Zugriff vor der Bildausgabe wieder her; die Funktion plant diesen Zeitpunkt nicht.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Ein PPUSTATUS-Lesezugriff kann das NMI-Timing und die nächsten Schreibzugriffe auf PPUSCROLL und PPUADDR beeinflussen. Fügen Sie ihn nicht zwischen ein erst halb geschriebenes Registerpaar einer anderen Routine ein. Diese Aufrufe warten nicht auf VBlank und arbeiten keine ausstehenden Übertragungsaufträge ab.
Anwendungsbeispiel
__scroll_x_set(0); // Keeps saved Y=27 while changing X to zero.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:116
Replace saved X and write an X/Y pair using the retained Y shadow.
kitaqfc/kitaqfc/CodeGenerator.cs:4717
case "__scroll_x_set":
if (!Need(1)) return true;
Store1(args[0], _runtimeScrollXAddress);
EmitAsm("LDA", Mem(0x2002)); EmitAsm("LDA", Mem(_runtimeScrollXAddress)); EmitAsm("STA", Mem(0x2005)); EmitAsm("LDA", Mem(_runtimeScrollYAddress)); EmitAsm("STA", Mem(0x2005)); return true;
// Changing Y rewrites both latch bytes, using the retained X component.__scroll_y_set
Ersetzt das gespeicherte Y, setzt den Latch durch einen Statuslesezugriff zurück und schreibt das gespeicherte X sowie das neue Y nach PPUSCROLL. Auch bei alleiniger Änderung von Y werden beide Scrollbytes geschrieben. X stammt aus der Laufzeitkopie.
Syntax
void __scroll_y_set(u8 y);Parameter
yDie Versätze sind vorzeichenlose Pixelbytes. Verwenden Sie X
0..255und normalerweise Y0..239; Y240..255wird unverändert geschrieben, hat aber ein besonderes PPU-Zeilenverhalten statt normalen sichtbaren Kartenbildlaufs. PPUCTRL liefert die Basis-Nametable-Bits. Stellen Sie den Bildlauf nach direktem VRAM-Zugriff vor der Bildausgabe wieder her; die Funktion plant diesen Zeitpunkt nicht.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Ein PPUSTATUS-Lesezugriff kann das NMI-Timing und die nächsten Schreibzugriffe auf PPUSCROLL und PPUADDR beeinflussen. Fügen Sie ihn nicht zwischen ein erst halb geschriebenes Registerpaar einer anderen Routine ein. Diese Aufrufe warten nicht auf VBlank und arbeiten keine ausstehenden Übertragungsaufträge ab.
Anwendungsbeispiel
__scroll_y_set(0); // Keeps saved X=0; the scene is now at the origin.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:118
Replace saved Y and write an X/Y pair using the retained X shadow.
kitaqfc/kitaqfc/CodeGenerator.cs:4722
case "__scroll_y_set":
if (!Need(1)) return true;
Store1(args[0], _runtimeScrollYAddress);
EmitAsm("LDA", Mem(0x2002)); EmitAsm("LDA", Mem(_runtimeScrollXAddress)); EmitAsm("STA", Mem(0x2005)); EmitAsm("LDA", Mem(_runtimeScrollYAddress)); EmitAsm("STA", Mem(0x2005)); return true;
// Modify only the NMI-enable bit in the PPUCTRL shadow and publish the new register value.__sdot2_q1_7
Berechnet ein vorzeichenbehaftetes Skalarprodukt mit zwei Termen.
Syntax
u16 __sdot2_q1_7(u16 a0, u16 a1, u8 b0, u8 b1);Parameter
a0Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.
a1Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.
b0Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.
b1Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.
Rückgabewert
Gibt die unteren 16 Bits von a0*b0+a1*b1 zurück.
Bedingungen und Grenzen
Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.
Anwendungsbeispiel
// Compare literal coefficients with values read from RAM.
result[34]=__sdot2_q1_7(-513,769,-64,127);
v0=-513;v1=769;v2=-64;v3=127;
result[35]=__sdot2_q1_7(v0,v1,v2,v3);
// Reverse coefficient signs and verify negative products.
result[36]=__sdot2_q1_7(-513,769,64,-127);
v0=-513;v1=769;v2=64;v3=-127;
result[37]=__sdot2_q1_7(v0,v1,v2,v3);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.
Der Ausschnitt speichert folgende 16-Bit-Werte: result[34]=64959, result[35]=64959, result[36]=577, result[37]=577. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chrImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:71
FC alias of __sdot2_q8_8: two raw signed-pattern products, summed without rescaling.
kitaqfc/kitaqfc/CodeGenerator.cs:4658
case "__sdot2_q1_7": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,1,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__sdot2_q8_8
Berechnet ein vorzeichenbehaftetes Skalarprodukt mit zwei Termen.
Syntax
u16 __sdot2_q8_8(u16 a0, u16 a1, u8 b0, u8 b1);Parameter
a0Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.
a1Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.
b0Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.
b1Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.
Rückgabewert
Gibt die unteren 16 Bits von a0*b0+a1*b1 zurück.
Bedingungen und Grenzen
Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.
Anwendungsbeispiel
// Compare literal coefficients with values read from RAM.
result[26]=__sdot2_q8_8(-513,769,-64,127);
v0=-513;v1=769;v2=-64;v3=127;
result[27]=__sdot2_q8_8(v0,v1,v2,v3);
// Reverse coefficient signs and verify negative products.
result[28]=__sdot2_q8_8(-513,769,64,-127);
v0=-513;v1=769;v2=64;v3=-127;
result[29]=__sdot2_q8_8(v0,v1,v2,v3);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.
Der Ausschnitt speichert folgende 16-Bit-Werte: result[26]=64959, result[27]=64959, result[28]=577, result[29]=577. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chrImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:67
Sum two signed-pattern products modulo 65536; no Q8.8 scaling shift is generated.
kitaqfc/kitaqfc/CodeGenerator.cs:4656
case "__sdot2_q8_8": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,1,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__sdot3_q1_7
Berechnet ein vorzeichenbehaftetes Skalarprodukt mit drei Termen.
Syntax
u16 __sdot3_q1_7(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);Parameter
a0Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.
a1Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.
a2Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.
b0Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.
b1Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.
b2Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.
Rückgabewert
Gibt die unteren 16 Bits von a0*b0+a1*b1+a2*b2 zurück.
Bedingungen und Grenzen
Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.
Anwendungsbeispiel
// Compare literal coefficients with values read from RAM.
result[38]=__sdot3_q1_7(-513,769,-1025,-64,127,-3);
v0=-513;v1=769;v2=-1025;v3=-64;v4=127;v5=-3;
result[39]=__sdot3_q1_7(v0,v1,v2,v3,v4,v5);
// Reverse coefficient signs and verify negative products.
result[40]=__sdot3_q1_7(-513,769,-1025,64,-127,3);
v0=-513;v1=769;v2=-1025;v3=64;v4=-127;v5=3;
result[41]=__sdot3_q1_7(v0,v1,v2,v3,v4,v5);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.
Der Ausschnitt speichert folgende 16-Bit-Werte: result[38]=2498, result[39]=2498, result[40]=63038, result[41]=63038. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chrImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:73
FC alias of __sdot3_q8_8: three raw signed-pattern products, summed without rescaling.
kitaqfc/kitaqfc/CodeGenerator.cs:4659
case "__sdot3_q1_7": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,2,1,1,1}, funcName);
// Pass a word seed to the RNG helper; the zero-argument generator is called directly below.
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__sdot3_q8_8
Berechnet ein vorzeichenbehaftetes Skalarprodukt mit drei Termen.
Syntax
u16 __sdot3_q8_8(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);Parameter
a0Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.
a1Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.
a2Vorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.
b0Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.
b1Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.
b2Vorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.
Rückgabewert
Gibt die unteren 16 Bits von a0*b0+a1*b1+a2*b2 zurück.
Bedingungen und Grenzen
Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.
Anwendungsbeispiel
// Compare literal coefficients with values read from RAM.
result[30]=__sdot3_q8_8(-513,769,-1025,-64,127,-3);
v0=-513;v1=769;v2=-1025;v3=-64;v4=127;v5=-3;
result[31]=__sdot3_q8_8(v0,v1,v2,v3,v4,v5);
// Reverse coefficient signs and verify negative products.
result[32]=__sdot3_q8_8(-513,769,-1025,64,-127,3);
v0=-513;v1=769;v2=-1025;v3=64;v4=-127;v5=3;
result[33]=__sdot3_q8_8(v0,v1,v2,v3,v4,v5);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.
Der Ausschnitt speichert folgende 16-Bit-Werte: result[30]=2498, result[31]=2498, result[32]=63038, result[33]=63038. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chrImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:69
Sum three signed-pattern products modulo 65536; choose operand units explicitly in the caller.
kitaqfc/kitaqfc/CodeGenerator.cs:4657
case "__sdot3_q8_8": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,2,1,1,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__serial_rx_bit
Tastet Bit 4 von $4017 einmal als Empfangsleitung eines externen Adapters ab.
Syntax
u8 __serial_rx_bit(void);Rückgabewert
1, wenn Bit 4 gesetzt ist, andernfalls 0. Das ist weder ein Bereitschaftssignal für Daten noch ein Kommunikationsfehlerstatus.
Bedingungen und Grenzen
Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.
Diese Grundoperationen setzen einen externen Adapter voraus, der OUT0 mit TX und $4017 D4 mit RX verbindet. Sie liefern weder Bitabstände noch einen Übertragungstakt, Start-/Stoppbits, die Zusammensetzung von Bytes oder eine Empfangswarteschleife. Sie erfüllen damit eine andere Aufgabe als byteorientierte Hilfsfunktionen wie __midi_out().
Der aufrufende Code muss zum passenden Zeitpunkt abtasten. Die Funktion wartet weder auf Daten noch setzt sie mehrere Bits zusammen; auch die Ausgabe von $4016 bleibt unverändert.
Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.
Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.
Anwendungsbeispiel
#include "fc_ports_scene.h"
__prg_rom u8 tx_values[6]={0,1,2,3,254,255};
void main(void){
u8 i;
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
for(i=0;i<6;i++)__serial_tx_bit(tx_values[i]);
__serial_tx_bit(1); // Leave OUT0 high; no byte framing or baud clock is added.
port_result[0]=__serial_rx_bit();port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"SERIAL BIT PRIMITIVES");m_wait();
m_text(2,5,"VALUE EXPECTED OUT0");m_wait();
for(i=0;i<6;i++){port_number(3,7+i*2,tx_values[i]);m_put(23,7+i*2,'0'+(tx_values[i]&1));m_wait();}
m_text(2,20,"RX SAMPLE");port_number(23,20,port_result[0]);m_wait();
m_text(2,23,"NO BYTE FRAMING OR CLOCK");m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Für VALUE 000/001/002/003/254/255 lautet EXPECTED OUT0 0/1/0/1/0/1. Die Spalte erläutert die Auswahl des niederwertigsten Bits; sie zeigt keinen zurückgelesenen elektrischen Pegel. CPU-Traces bestätigen diese Schreibzugriffe und ein abschließendes Schreiben von 1. RX SAMPLE ist 000, da KUROSAKI die Empfangsleitung nicht emuliert.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_serial.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_serial.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_serial.nes --frames 120 --png .\out\peripheral_serial.pngImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:288
Read 4017 bit 4 and normalize it to 0 or 1; an external serial adapter supplies the signal.
kitaqfc/kitaqfc/CodeGenerator.cs:7965
EmitHelperStart("__serial_rx_bit");
EmitAsm("LDA", Mem(0x4017));
EmitAsm("AND", Imm(0x10));
string rx0 = NewGeneratedLabel("serial_rx0");
string rxd = NewGeneratedLabel("serial_rxdone");
EmitAsm("BEQ", Rel(rx0));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(rxd));
_assembly.Add(Expr.Make(Tag.Label, rx0));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, rxd));
EmitAsm("RTS");__serial_tx_bit
Schreibt das niederwertigste Bit des Arguments über $4016 auf OUT0. Damit steht eine einzelne Bitausgabe für einen externen seriellen Adapter zur Verfügung.
Syntax
void __serial_tx_bit(u8 bit);Parameter
bitAuszugebender Wert. Verwendet wird nur
bit & 1: 2 gibt null aus, 255 dagegen eins.
Rückgabewert
Kein Rückgabewert.
Bedingungen und Grenzen
Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.
Diese Grundoperationen setzen einen externen Adapter voraus, der OUT0 mit TX und $4017 D4 mit RX verbindet. Sie liefern weder Bitabstände noch einen Übertragungstakt, Start-/Stoppbits, die Zusammensetzung von Bytes oder eine Empfangswarteschleife. Sie erfüllen damit eine andere Aufgabe als byteorientierte Hilfsfunktionen wie __midi_out().
Schreibt null oder eins in das gesamte Register $4016 und löscht dabei OUT1/OUT2. Eine Haltezeit wird nicht eingefügt. OUT0 steuert die Übernahme der Tastenwerte beim normalen Gamepad; der Vorgang verändert daher auch dessen Lesezustand.
Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.
Anwendungsbeispiel
#include "fc_ports_scene.h"
__prg_rom u8 tx_values[6]={0,1,2,3,254,255};
void main(void){
u8 i;
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
for(i=0;i<6;i++)__serial_tx_bit(tx_values[i]);
__serial_tx_bit(1); // Leave OUT0 high; no byte framing or baud clock is added.
port_result[0]=__serial_rx_bit();port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"SERIAL BIT PRIMITIVES");m_wait();
m_text(2,5,"VALUE EXPECTED OUT0");m_wait();
for(i=0;i<6;i++){port_number(3,7+i*2,tx_values[i]);m_put(23,7+i*2,'0'+(tx_values[i]&1));m_wait();}
m_text(2,20,"RX SAMPLE");port_number(23,20,port_result[0]);m_wait();
m_text(2,23,"NO BYTE FRAMING OR CLOCK");m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Für VALUE 000/001/002/003/254/255 lautet EXPECTED OUT0 0/1/0/1/0/1. Die Spalte erläutert die Auswahl des niederwertigsten Bits; sie zeigt keinen zurückgelesenen elektrischen Pegel. CPU-Traces bestätigen diese Schreibzugriffe und ein abschließendes Schreiben von 1. RX SAMPLE ist 000, da KUROSAKI die Empfangsleitung nicht emuliert.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_serial.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_serial.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_serial.nes --frames 120 --png .\out\peripheral_serial.pngImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:286
Expansion-port TTL serial / MIDI adapter support. The low-level serial layer assumes an external adapter that maps OUT0 to TX and D4 on $4017 to RX. MIDI helpers bit-bang 8N1 bytes at an approximate 31250 bps timing suitable for adapter bring-up and simple music sync. Write bit & 1 to 4016, replacing the port output value; coordinate with other port users.
kitaqfc/kitaqfc/CodeGenerator.cs:7958
EmitHelperStart("__serial_tx_bit");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("AND", Imm(1));
EmitAsm("STA", Mem(0x4016));
EmitAsm("RTS");__smac16
Berechnet eine vorzeichenbehaftete Multiplikation mit Akkumulation.
Syntax
u16 __smac16(u16 acc, u16 a, u8 b);Parameter
accZu addierender 16-Bit-Akkumulator.
aVorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.
bVorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.
Rückgabewert
Gibt die unteren 16 Bits von acc+a*b zurück.
Bedingungen und Grenzen
Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.
Anwendungsbeispiel
// Compare literal coefficients with values read from RAM.
result[10]=__smac16(65500,-513,-64);
v0=65500;v1=-513;v2=-64;
result[11]=__smac16(v0,v1,v2);
// Reverse coefficient signs and verify negative products.
result[12]=__smac16(65500,-513,64);
v0=65500;v1=-513;v2=64;
result[13]=__smac16(v0,v1,v2);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.
Der Ausschnitt speichert folgende 16-Bit-Werte: result[10]=32796, result[11]=32796, result[12]=32668, result[13]=32668. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chrImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:57
Add the signed-pattern product to acc modulo 65536; the API carries the result as u16 bits.
kitaqfc/kitaqfc/CodeGenerator.cs:4651
case "__smac16": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__smac16_q1_7
Berechnet eine vorzeichenbehaftete Multiplikation mit Akkumulation.
Syntax
u16 __smac16_q1_7(u16 acc, u16 a, u8 b);Parameter
accZu addierender 16-Bit-Akkumulator.
aVorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.
bVorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.
Rückgabewert
Gibt die unteren 16 Bits von acc+a*b zurück.
Bedingungen und Grenzen
Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.
Anwendungsbeispiel
// Compare literal coefficients with values read from RAM.
result[18]=__smac16_q1_7(65500,-513,-64);
v0=65500;v1=-513;v2=-64;
result[19]=__smac16_q1_7(v0,v1,v2);
// Reverse coefficient signs and verify negative products.
result[20]=__smac16_q1_7(65500,-513,64);
v0=65500;v1=-513;v2=64;
result[21]=__smac16_q1_7(v0,v1,v2);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.
Der Ausschnitt speichert folgende 16-Bit-Werte: result[18]=32796, result[19]=32796, result[20]=32668, result[21]=32668. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chrImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:61
FC alias of __smac16: accumulate a raw signed-pattern product modulo 65536.
kitaqfc/kitaqfc/CodeGenerator.cs:4653
case "__smac16_q1_7": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__smul16x8
Berechnet eine vorzeichenbehaftete Multiplikation.
Syntax
u16 __smul16x8(u16 a, u8 b);Parameter
aVorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.
bVorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.
Rückgabewert
Gibt die unteren 16 Bits von a*b zurück.
Bedingungen und Grenzen
Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.
Anwendungsbeispiel
// Compare literal coefficients with values read from RAM.
result[4]=__smul16x8(-513,-64);
v0=-513;v1=-64;
result[5]=__smul16x8(v0,v1);
// Reverse coefficient signs and verify negative products.
result[6]=__smul16x8(-513,64);
v0=-513;v1=64;
result[7]=__smul16x8(v0,v1);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.
Der Ausschnitt speichert folgende 16-Bit-Werte: result[4]=32832, result[5]=32832, result[6]=32704, result[7]=32704. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chrImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:53
Interpret a and b as signed two's-complement bit patterns and return the low word of their product.
kitaqfc/kitaqfc/CodeGenerator.cs:4648
case "__smul16x8": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__smul16x8_q1_7
Berechnet eine vorzeichenbehaftete Multiplikation.
Syntax
u16 __smul16x8_q1_7(u16 a, u8 b);Parameter
aVorzeichenbehaftetes 16-Bit-Bitmuster für −32768..32767.
bVorzeichenbehaftetes Byte-Bitmuster für einen Koeffizienten von −128..127.
Rückgabewert
Gibt die unteren 16 Bits von a*b zurück.
Bedingungen und Grenzen
Auf dem FC verwenden die q8_8/q1_7-Funktionen rohe Ganzzahlprodukte ohne Festkomma-Rechtsshift. Q1.7-Funktionen sind Aliase der entsprechenden vorzeichenbehafteten Operationen. Der aufrufende Code verwaltet die Einheiten; vorzeichenbehaftete Ergebnisse werden als u16-Bitmuster zurückgegeben.
Anwendungsbeispiel
// Compare literal coefficients with values read from RAM.
result[14]=__smul16x8_q1_7(-513,-64);
v0=-513;v1=-64;
result[15]=__smul16x8_q1_7(v0,v1);
// Reverse coefficient signs and verify negative products.
result[16]=__smul16x8_q1_7(-513,64);
v0=-513;v1=64;
result[17]=__smul16x8_q1_7(v0,v1);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Vergleichen Sie Literalwerte mit im RAM gespeicherten Werten. Repräsentative Ergebnisse erscheinen als vorzeichenlose 16-Bit-Werte; negative Werte nutzen Zweierkomplement-Bitmuster. Der GB skaliert Festkommaprodukte, der FC liefert Ganzzahlprodukte. Gleiche Eingaben müssen daher nicht gleiche Ergebnisse liefern.
Der Ausschnitt speichert folgende 16-Bit-Werte: result[14]=32832, result[15]=32832, result[16]=32704, result[17]=32704. Jede Berechnung mit Literalwerten stimmt mit der entsprechenden Berechnung aus RAM-Werten überein. Der Bildschirm zeigt repräsentative Werte; das Array result enthält alle Ergebnisse.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_math.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_math.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chrImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:59
FC alias of __smul16x8: signed-pattern raw product, with no Q1.7 scaling shift.
kitaqfc/kitaqfc/CodeGenerator.cs:4652
case "__smul16x8_q1_7": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,1}, funcName);
void EmitHelperMath()
{
// Consume eight multiplier bits and accumulate the low product word in shared temporaries, returning A/X.
EmitHelperStart("__mul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDX", Imm(8));
string loop = NewGeneratedLabel("mul16x8_loop");
string skip = NewGeneratedLabel("mul16x8_skip_add");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LSR", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(skip));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("CLC");
EmitAsm("ADC", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("ADC", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, skip));
EmitAsm("ASL", Mem(CallArgBase));
EmitAsm("ROL", Mem(CallArgBase + 1));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
// Zero-extend the byte multiplicand, reuse word multiplication and return its high byte.
EmitHelperStart("__mul8x8_hi");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("TXA");
EmitAsm("RTS");
// Convert signed operands to magnitudes, multiply, then restore the product sign modulo 65536.
EmitHelperStart("__smul16x8");
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
string aPos = NewGeneratedLabel("smul_a_positive");
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(aPos));
EmitAsm("LDA", Mem(CallArgBase)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, aPos));
string bPos = NewGeneratedLabel("smul_b_positive");
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("AND", Imm(0x80));
EmitAsm("BEQ", Rel(bPos));
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address)); EmitAsm("EOR", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
_assembly.Add(Expr.Make(Tag.Label, bPos));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("STX", Mem(_runtimeIntrinsicTmp1Address));
string sdone = NewGeneratedLabel("smul_done");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("BEQ", Rel(sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("CLC"); EmitAsm("ADC", Imm(1)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address)); EmitAsm("EOR", Imm(0xFF)); EmitAsm("ADC", Imm(0)); EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
_assembly.Add(Expr.Make(Tag.Label, sdone));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LDX", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("RTS");
EmitMacHelper("__mac16", "__mul16x8");
EmitMacHelper("__smac16", "__smul16x8");
EmitDot2Helper("__dot2_q8_8", "__mul16x8");
EmitDot3Helper("__dot3_q8_8", "__mul16x8");
EmitDot2Helper("__sdot2_q8_8", "__smul16x8");
EmitDot3Helper("__sdot3_q8_8", "__smul16x8");
}__split_scroll_sprite0
Wartet auf einen neuen Sprite-null-Treffer und schreibt Scroll-X/Y für eine horizontale Teilung: oben ausgerichtet, unten verschoben.
Syntax
void __split_scroll_sprite0(u8 x, u8 y);Parameter
xHorizontaler Scrollwert 0..255, keine Objekt-Bildschirm-X-Koordinate. Das Beispiel nutzt 32, um den unteren Teil desselben Hintergrunds 32 Pixel nach links zu verschieben.
yVorübergehender vertikaler Scrollwert 0..255; üblicher sichtbarer Bereich 0..239. Die Hilfe schreibt $2005 und ändert daher während der Darstellung die laufende Vertikalposition nicht sofort. Das horizontale Teilungsbeispiel übergibt null.
Rückgabewert
Kein Rückgabewert.
Bedingungen und Grenzen
Überlagern Sie ein deckendes Pixel von OAM-Sprite 0 mit einem deckenden Hintergrundpixel bei aktivierten Ebenen. Die Priorität hinter dem Hintergrund erlaubt weiterhin einen Treffer. Die linken acht Pixel erfordern die jeweiligen Ebenenfreigaben; Überlappung bei X=255 löst keinen Treffer aus.
Fragt PPUSTATUS ($2002), Bit 6 ab, bis es gelöscht und danach gesetzt ist. Bei bereits erfolgtem Treffer wartet der Aufruf auf einen neuen im nächsten Frame. Lesen quittiert das Flag nicht; die PPU löscht es während Pre-Render.
Keine Zeitbegrenzung oder Fehlerrückgabe. Verstecktes Sprite 0, transparenter Hintergrund, deaktivierte Darstellung oder sonst fehlende Überlappung lassen den Aufruf blockieren. Stellen Sie vorher einen Treffer sicher. CPU-IRQ-Freigabe ist nicht nötig, NMI wird nicht aktiviert. Das Beispiel aktiviert NMI für seine separate Framewartefunktion.
Lesen von $2002 beeinflusst auch das gemeinsame PPU-Adress-/Scrollschreiblatch und das VBlank-Flag. Fügen Sie diesen Aufruf nicht zwischen die beiden Schreibzugriffe einer $2005-/$2006-Operation ein. Nutzen Sie Hauptcode, keinen NMI-/IRQ-Handler.
Nach dem Treffer wird $2002 gelesen und X, dann Y nach $2005 geschrieben. Fein-X wirkt sofort, Grob-X beim nächsten horizontalen Nachladen. Y wirkt normalerweise beim nächsten Pre-Render-Nachladen, sofern kein anderer Scrollzugriff es vorher ersetzt. Dies ist keine sofortige beliebige X/Y-Rasterneupositionierung.
Polling und Befehlsausführung verzögern die Schreibzugriffe. KUROSAKI rendert zeilenweise; die Aufnahmen belegen daher keine punktgenaue Hardwaregrenze. Das Beispiel legt den Wechsel in das weiße Band Y=96..103, abseits der blauen und roten Balken.
Der gemeinsame Header fc_sprite0_scene.h bereitet eigene blaue/rote Balken und ein weißes horizontales Band vor. Sprite 0 liegt bei (16,96) hinter dem Hintergrund; sein rohes OAM-Y ist 95, da Anzeige-Y=OAM-Y+1. Jeder Frame setzt während VBlank den oberen Scroll auf null zurück und wartet dann auf den Treffer.
NESdev: PPU-Referenz für Programmierer
Anwendungsbeispiel
void main(void) {
s0_prepare();
while (1) {
s0_frame();
__split_scroll_sprite0(32,0);
s0_completed++;
}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Über dem weißen Band liegt Blau bei X=64..95 und Rot bei X=160..191. Darunter ab Y=104 liegt Blau bei X=32..63, Rot bei X=128..159; nur der untere Bereich wandert 32 Pixel nach links. Das Band liegt bei Y=96..103, die Balken enden bei Y=207. Geprüft werden ein Abschluss pro Frame sowie Position und Farbe jedes Bildpixels.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sprite0_split.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\sprite0_split.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\sprite0_scene.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:336
Wait for a new sprite-zero hit, reset the PPU latch and write X/Y scroll; requires a visible hit setup.
__sprite0_wait_hit
Wartet auf neue Sprite-null-/Hintergrundüberlappung und synchronisiert Hauptarbeit auf einen bekannten Bildschirmbereich, ohne Scrollwerte zu ändern.
Syntax
void __sprite0_wait_hit(void);Rückgabewert
Kein Rückgabewert.
Bedingungen und Grenzen
Überlagern Sie ein deckendes Pixel von OAM-Sprite 0 mit einem deckenden Hintergrundpixel bei aktivierten Ebenen. Die Priorität hinter dem Hintergrund erlaubt weiterhin einen Treffer. Die linken acht Pixel erfordern die jeweiligen Ebenenfreigaben; Überlappung bei X=255 löst keinen Treffer aus.
Fragt PPUSTATUS ($2002), Bit 6 ab, bis es gelöscht und danach gesetzt ist. Bei bereits erfolgtem Treffer wartet der Aufruf auf einen neuen im nächsten Frame. Lesen quittiert das Flag nicht; die PPU löscht es während Pre-Render.
Keine Zeitbegrenzung oder Fehlerrückgabe. Verstecktes Sprite 0, transparenter Hintergrund, deaktivierte Darstellung oder sonst fehlende Überlappung lassen den Aufruf blockieren. Stellen Sie vorher einen Treffer sicher. CPU-IRQ-Freigabe ist nicht nötig, NMI wird nicht aktiviert. Das Beispiel aktiviert NMI für seine separate Framewartefunktion.
Lesen von $2002 beeinflusst auch das gemeinsame PPU-Adress-/Scrollschreiblatch und das VBlank-Flag. Fügen Sie diesen Aufruf nicht zwischen die beiden Schreibzugriffe einer $2005-/$2006-Operation ein. Nutzen Sie Hauptcode, keinen NMI-/IRQ-Handler.
Polling und Befehlsausführung verzögern die Schreibzugriffe. KUROSAKI rendert zeilenweise; die Aufnahmen belegen daher keine punktgenaue Hardwaregrenze. Das Beispiel legt den Wechsel in das weiße Band Y=96..103, abseits der blauen und roten Balken.
Der gemeinsame Header fc_sprite0_scene.h bereitet eigene blaue/rote Balken und ein weißes horizontales Band vor. Sprite 0 liegt bei (16,96) hinter dem Hintergrund; sein rohes OAM-Y ist 95, da Anzeige-Y=OAM-Y+1. Jeder Frame setzt während VBlank den oberen Scroll auf null zurück und wartet dann auf den Treffer.
NESdev: PPU-Referenz für Programmierer
Anwendungsbeispiel
void main(void) {
s0_prepare();
while (1) {
s0_frame();
__sprite0_wait_hit();
s0_completed++;
}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Der blaue Balken belegt X=64..95, der rote X=160..191, ober- und unterhalb des weißen Bands ausgerichtet. Warten allein verändert den Scroll nicht. Das ausgeführte Beispiel beendet 116 Wartevorgänge nach 120 Frames und 117 nach 121 und bestätigt so den Fortschritt.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\sprite0_wait.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\sprite0_wait.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\sprite0_scene.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:334
Wait for the current hit flag to clear, then for a new hit. No timeout is provided.
__sprite_attr
Ersetzt das gesamte Attributbyte eines Sprites im Schattenpuffer. Damit lassen sich Palette, Priorität gegenüber dem Hintergrund sowie horizontale und vertikale Spiegelung ändern, ohne Kachel oder Position zu verändern.
Syntax
void __sprite_attr(u8 index, u8 attr);Parameter
indexSprite-Index von 0 bis 63, kein Byte-Offset. Intern wird er mit vier multipliziert und als 8-Bit-Position gespeichert. Werte außerhalb des Bereichs werden modulo 64 umgebrochen und können einen anderen Eintrag überschreiben. Eine Belegungsprüfung findet nicht statt.
attrVollständiges Attributbyte: Bits 0–1 wählen Palette 0–3; Bit 5 legt undurchsichtige Sprite-Pixel hinter undurchsichtige Hintergrundpixel; Bits 6 und 7 spiegeln horizontal beziehungsweise vertikal. Bits 2–4 müssen null bleiben. Kombinieren Sie Flags mit bitweisem ODER; das Byte ersetzt sämtliche bisherigen Attribute.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Diese Operationen ändern den CPU-RAM bei 0x0200–0x02FF, nicht sofort das Hardware-OAM. Übertragen Sie die fertige Seite mit __oam_dma. Belegungsflags, Koordinatenfelder und Zähler der Sprite-Bibliothek werden nicht mitgeführt. Interrupt-Routinen dürfen eine noch unvollständige Seite weder ändern noch übertragen.
Anwendungsbeispiel
__sprite_attr(3,0xC0); // Flip the arrow horizontally and vertically.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.
TRANSFER zeigt einen Pfeil bei (144,128). Dieses Programm überträgt Seite 02 direkt mit __oam_dma, einschließlich aller durch die einzelnen Sprite-Operationen vorbereiteten Felder.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:198
Replace the selected shadow attribute byte, including palette, priority and flip bits.
EmitHelperStart("__sprite_attr");
EmitSpriteIndexToX();
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("STA", new AsmOperand(OamRamBase + 2, AddressMode.AbsoluteX));
EmitAsm("RTS");__sprite_hide
Blendet einen Sprite im Schattenpuffer aus, indem Y auf 0xF0 gesetzt wird. X, Kachel und Attribute bleiben zur Wiederverwendung erhalten. Die Funktion gibt keinen Platz frei und ändert den Belegungszähler der Sprite-Bibliothek nicht.
Syntax
void __sprite_hide(u8 index);Parameter
indexSprite-Index von 0 bis 63, kein Byte-Offset. Intern wird er mit vier multipliziert und als 8-Bit-Position gespeichert. Werte außerhalb des Bereichs werden modulo 64 umgebrochen und können einen anderen Eintrag überschreiben. Eine Belegungsprüfung findet nicht statt.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Diese Operationen ändern den CPU-RAM bei 0x0200–0x02FF, nicht sofort das Hardware-OAM. Übertragen Sie die fertige Seite mit __oam_dma. Belegungsflags, Koordinatenfelder und Zähler der Sprite-Bibliothek werden nicht mitgeführt. Interrupt-Routinen dürfen eine noch unvollständige Seite weder ändern noch übertragen.
Anwendungsbeispiel
__sprite_hide(4); // The HIDE row remains empty after DMA.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.
TRANSFER zeigt einen Pfeil bei (144,128). Dieses Programm überträgt Seite 02 direkt mit __oam_dma, einschließlich aller durch die einzelnen Sprite-Operationen vorbereiteten Felder.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:200
Write F0 to the selected shadow Y byte; issue DMA to make the change visible.
EmitHelperStart("__sprite_hide");
EmitSpriteIndexToX();
EmitAsm("LDA", Imm(0xF0)); EmitAsm("STA", new AsmOperand(OamRamBase + 0, AddressMode.AbsoluteX));
EmitAsm("RTS");__sprite_move
Ändert nur die X- und Y-Bytes eines Sprites im Schattenpuffer. Kachelnummer und Attribute bleiben erhalten; dieselbe Grafik wird ohne Änderung der Palette oder Spiegelung verschoben.
Syntax
void __sprite_move(u8 index, u8 x, u8 y);Parameter
indexSprite-Index von 0 bis 63, kein Byte-Offset. Intern wird er mit vier multipliziert und als 8-Bit-Position gespeichert. Werte außerhalb des Bereichs werden modulo 64 umgebrochen und können einen anderen Eintrag überschreiben. Eine Belegungsprüfung findet nicht statt.
xHorizontale Position in Pixeln als vorzeichenloses Byte; keine Tile-Koordinate.
yY-Byte des OAM. Für eine Oberkante unterhalb der Zeile null wird ihre Position minus eins übergeben; 239..255 liegen außerhalb der 240 sichtbaren Zeilen.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Diese Operationen ändern den CPU-RAM bei 0x0200–0x02FF, nicht sofort das Hardware-OAM. Übertragen Sie die fertige Seite mit __oam_dma. Belegungsflags, Koordinatenfelder und Zähler der Sprite-Bibliothek werden nicht mitgeführt. Interrupt-Routinen dürfen eine noch unvollständige Seite weder ändern noch übertragen.
Anwendungsbeispiel
__sprite_move(1,144,31); // Move to (144,32), retaining tile 128 and attributes 0.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.
TRANSFER zeigt einen Pfeil bei (144,128). Dieses Programm überträgt Seite 02 direkt mit __oam_dma, einschließlich aller durch die einzelnen Sprite-Operationen vorbereiteten Felder.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:194
Replace only shadow X/Y for index 0..63; tile and attribute bytes remain unchanged.
EmitHelperStart("__sprite_move");
EmitSpriteIndexToX();
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("STA", new AsmOperand(OamRamBase + 0, AddressMode.AbsoluteX));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("STA", new AsmOperand(OamRamBase + 3, AddressMode.AbsoluteX));
EmitAsm("RTS");__sprite_set
Schreibt alle vier Felder eines Sprites in den Schattenpuffer der Seite 02. Die Argumente lauten Index, X, Y, Kachel und Attribute; gespeichert wird in der Reihenfolge Y, Kachel, Attribute, X.
Syntax
void __sprite_set(u8 index, u8 x, u8 y, u8 tile, u8 attr);Parameter
indexSprite-Index von 0 bis 63, kein Byte-Offset. Intern wird er mit vier multipliziert und als 8-Bit-Position gespeichert. Werte außerhalb des Bereichs werden modulo 64 umgebrochen und können einen anderen Eintrag überschreiben. Eine Belegungsprüfung findet nicht statt.
xHorizontale Position in Pixeln als vorzeichenloses Byte; keine Tile-Koordinate.
yY-Byte des OAM. Für eine Oberkante unterhalb der Zeile null wird ihre Position minus eins übergeben; 239..255 liegen außerhalb der 240 sichtbaren Zeilen.
tile8-Bit-Tile-Nummer. Die Grafik muss vorher geladen werden. Die Hardware-Regeln für 8×16-Sprites gelten weiterhin; die Nummer wird nicht für Tile-Paare angepasst.
attrVollständiges Attributbyte: Bits 0–1 wählen Palette 0–3; Bit 5 legt undurchsichtige Sprite-Pixel hinter undurchsichtige Hintergrundpixel; Bits 6 und 7 spiegeln horizontal beziehungsweise vertikal. Bits 2–4 müssen null bleiben. Kombinieren Sie Flags mit bitweisem ODER; das Byte ersetzt sämtliche bisherigen Attribute.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Diese Operationen ändern den CPU-RAM bei 0x0200–0x02FF, nicht sofort das Hardware-OAM. Übertragen Sie die fertige Seite mit __oam_dma. Belegungsflags, Koordinatenfelder und Zähler der Sprite-Bibliothek werden nicht mitgeführt. Interrupt-Routinen dürfen eine noch unvollständige Seite weder ändern noch übertragen.
Anwendungsbeispiel
__sprite_set(0,144,15,128,0); // An 8x8 arrow at visible position (144,16).Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.
TRANSFER zeigt einen Pfeil bei (144,128). Dieses Programm überträgt Seite 02 direkt mit __oam_dma, einschließlich aller durch die einzelnen Sprite-Operationen vorbereiteten Felder.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:192
Write Y, tile, attributes and X to one shadow entry. Use indices 0..63; coordinates are raw OAM bytes.
EmitHelperStart("__sprite_set");
EmitSpriteIndexToX();
EmitAsm("LDA", Mem(CallArgBase + 2)); EmitAsm("STA", new AsmOperand(OamRamBase + 0, AddressMode.AbsoluteX));
EmitAsm("LDA", Mem(CallArgBase + 3)); EmitAsm("STA", new AsmOperand(OamRamBase + 1, AddressMode.AbsoluteX));
EmitAsm("LDA", Mem(CallArgBase + 4)); EmitAsm("STA", new AsmOperand(OamRamBase + 2, AddressMode.AbsoluteX));
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("STA", new AsmOperand(OamRamBase + 3, AddressMode.AbsoluteX));
EmitAsm("RTS");__sprite_tile
Ersetzt nur die Kachelnummer eines Sprites im Schattenpuffer. Position und Attribute bleiben erhalten. Dabei wird ein vorhandenes Muster ausgewählt; es werden keine Grafikdaten in den CHR-Speicher übertragen.
Syntax
void __sprite_tile(u8 index, u8 tile);Parameter
indexSprite-Index von 0 bis 63, kein Byte-Offset. Intern wird er mit vier multipliziert und als 8-Bit-Position gespeichert. Werte außerhalb des Bereichs werden modulo 64 umgebrochen und können einen anderen Eintrag überschreiben. Eine Belegungsprüfung findet nicht statt.
tile8-Bit-Tile-Nummer. Die Grafik muss vorher geladen werden. Die Hardware-Regeln für 8×16-Sprites gelten weiterhin; die Nummer wird nicht für Tile-Paare angepasst.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Diese Operationen ändern den CPU-RAM bei 0x0200–0x02FF, nicht sofort das Hardware-OAM. Übertragen Sie die fertige Seite mit __oam_dma. Belegungsflags, Koordinatenfelder und Zähler der Sprite-Bibliothek werden nicht mitgeführt. Interrupt-Routinen dürfen eine noch unvollständige Seite weder ändern noch übertragen.
Anwendungsbeispiel
__sprite_tile(2,129); // Select the green square at (144,48).Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Beispiel zeigt jede Operation in einer beschrifteten Zeile. SET und MOVE zeigen 8×8-Pfeile nach rechts bei (144,16) und (144,32), TILE ein grünes Quadrat bei (144,48) und ATTR den in beiden Achsen gespiegelten Pfeil bei (144,64). HIDE bei (144,80) und CLEAR bei (144,112) müssen leer bleiben. META zeigt zwei Pfeile bei (136,96) und (144,96), den zweiten horizontal gespiegelt. NEXT 07 bezeichnet die nächste Position nach den Einträgen 5 und 6. Die Ränder sind rot, der Schaft grün und die Spitzendetails blau. Geprüft werden 8.704 Pixel je Bild und die übertragenen OAM-Bytes; echte Hardware wurde nicht getestet.
TRANSFER zeigt einen Pfeil bei (144,128). Dieses Programm überträgt Seite 02 direkt mit __oam_dma, einschließlich aller durch die einzelnen Sprite-Operationen vorbereiteten Felder.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\oam_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\sprite_example.chr -o .\out\oam_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:196
Replace the selected shadow tile byte; this does not upload tile patterns.
EmitHelperStart("__sprite_tile");
EmitSpriteIndexToX();
EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("STA", new AsmOperand(OamRamBase + 1, AddressMode.AbsoluteX));
EmitAsm("RTS");__vram_fill
Setzt ppu_addr und schreibt anschließend dasselbe Byte value genau len-mal nach PPUDATA. Das direkte Füllen ist bei der Rückkehr abgeschlossen. Kachelmaße, Attribute und Rechteckgrenzen werden dabei nicht ausgewertet.
Syntax
void __vram_fill(u16 ppu_addr, u8 value, u8 len);Parameter
ppu_addrPPU-Byteadresse (
0x0000–0x3FFF), kein CPU-Zeiger. Das Ziel muss beschreibbar sein: Musteränderungen benötigen CHR-RAM statt CHR-ROM. PPU-Adressabbildung und Spiegelung gelten weiterhin. Wählen Sie für fortlaufendes Kopieren und Füllen die Schrittweite 1 in PPUCTRL. Führen Sie Befehle zu einem sicheren PPU-Zeitpunkt aus und stellen Sie danach den Scrollzustand her; der Ausführer schreibt PPUADDR und PPUDATA, ohne Scrolleinstellungen wiederherzustellen.valueEin wiederholtes Byte von 0 bis 255. Seine sichtbare Wirkung hängt davon ab, ob das Ziel Tile-Indizes, Pixelebenen oder eine andere Bytestruktur enthält.
lenByteanzahl
0..255. Auch null setzt die PPU-Adresse und den Latch zurück, schreibt aber keine Daten. Zielkapazität und Grenzen werden nicht geprüft; Bankwechsel und verzögerte Übertragung sind nicht enthalten.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.
Anwendungsbeispiel
__vram_fill(0x2060,2,32); // A second blue row of 4-pixel-wide left halves.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:122
Write repeated bytes directly; this is not an enqueue operation.
kitaqfc/kitaqfc/CodeGenerator.cs:4818
case "__vram_fill": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,1,1}, funcName);
// Nametable and attribute helpers receive byte coordinates, values and optional table selectors.
void EmitHelperVramFill()
{
EmitHelperStart("__vram_fill");
EmitPpuAddrFromCallArg();
EmitAsm("LDX", Mem(CallArgBase + 3));
string loop = NewGeneratedLabel("vram_fill_loop");
string done = NewGeneratedLabel("vram_fill_done");
EmitAsm("BEQ", Rel(done));
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("LDA", Mem(CallArgBase + 2));
EmitAsm("STA", Mem(0x2007));
EmitAsm("DEX");
EmitAsm("BNE", Rel(loop));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__vram_write
Setzt den PPU-Latch zurück, setzt ppu_addr und kopiert synchron len aufeinanderfolgende Quellbytes nach PPUDATA. Die Anzahl ist acht Bit breit; der Compiler verwendet indizierte Quellzugriffe. Kein Zeiger wird für eine spätere Ausführung gespeichert. Änderungen der Quelle nach der Rückkehr verändern VRAM nicht.
Syntax
void __vram_write(u16 ppu_addr, const u8* src, u8 len);Parameter
ppu_addrPPU-Byteadresse (
0x0000–0x3FFF), kein CPU-Zeiger. Das Ziel muss beschreibbar sein: Musteränderungen benötigen CHR-RAM statt CHR-ROM. PPU-Adressabbildung und Spiegelung gelten weiterhin. Wählen Sie für fortlaufendes Kopieren und Füllen die Schrittweite 1 in PPUCTRL. Führen Sie Befehle zu einem sicheren PPU-Zeitpunkt aus und stellen Sie danach den Scrollzustand her; der Ausführer schreibt PPUADDR und PPUDATA, ohne Scrolleinstellungen wiederherzustellen.srcCPU-Zeiger auf mindestens die angeforderte Anzahl lesbarer Bytes. Halten Sie die Quelle bis zur Rückkehr dieser synchronen Übertragung eingeblendet und unverändert. Es gibt keine Bankumschaltung, Speicherung des Zeigers in einer Warteschlange oder Prüfung der Quelllänge. Bei Anzahl null wird der Zeiger nicht dereferenziert.
lenByteanzahl
0..255. Auch null setzt die PPU-Adresse und den Latch zurück, schreibt aber keine Daten. Zielkapazität und Grenzen werden nicht geprüft; Bankwechsel und verzögerte Übertragung sind nicht enthalten.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Direkter PPU-Zugriff erfordert ausgeschaltete Bildausgabe oder einen sicheren VBlank-Zeitraum, in den die gesamte Operation passt. Diese Funktionen warten nicht, legen keine Aufträge an, sperren NMI nicht und stellen den Bildlauf nicht wieder her. Verhindern Sie, dass Interrupts während des Aufrufs den gemeinsamen Latch oder die Adresse verändern. PPUCTRL-Bit 2 wählt eine Zieladressschrittweite von 1 oder 32 pro Byte. Spiegelung und Adressumlauf übernimmt die PPU; CHR-ROM ist nicht beschreibbar. Stellen Sie nach dem Zugriff vor der Bildausgabe den gewünschten Bildlauf ein.
Anwendungsbeispiel
__vram_write(0x2040,pattern,32); // Blue row: solid tile, left half, triangle.
pattern[0]=3; // The completed transfer keeps its first solid tile.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Registereinstellungen, direkte Kachel- und Attributzugriffe, Bildlauf sowie getrennte Hintergrund- und Spritepaletten. Der blaue Streifen bei (0,16) ist 256×16 Pixel groß: Die erste Zeile wiederholt eine volle 8×8-Kachel, einen linken 4×8-Balken und ein achtzeiliges Dreieck; die zweite enthält nur linke Balken. Bei (16,48) beginnt ein gefülltes grünes 64×32-Rechteck. Der rote 64×32-Bereich bei (144,48) enthält ein Raster aus 8 mal 4 Dreieckskacheln.
Bei y=96 stehen blaue Markierungen an x=16, 24 und 32 als Quadrat, linker Balken und Dreieck; bei x=80 ein roter linker Balken und bei x=144 ein grünes Dreieck. Dies sind Hintergrundkacheln. Die Sprites mit eigener Palette stehen bei y=144: ein rotes 8×8-Quadrat an x=16, ein blauer linker 4×8-Balken an x=80 und ein grünes achtzeiliges Dreieck an x=144. Der endgültige Bildlauf ist (0,0).
Das Bild prüft Formen und Farben an 16.384 Pixeln einschließlich der Sprites. Weitere 31 Zustandsprüfungen decken Register, Status-/Latch-Nebenwirkungen, gespeicherten Bildlauf, 0/255-Byte-Transfers, Schrittweiten 1/32, Kartenränder, Nullmaße, Tabellenwahl, vollständige Attribute und beide Paletten ab. Y=255 wird als Registerwert geprüft, nicht als korrektes Scrollbild. Die Nachweise stammen aus KUROSAKI mit NROM; echte Hardware wurde nicht getestet.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\ppu_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr -o .\out\ppu_intrinsics.nes --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:120
Write a byte-counted source range directly; arrange PPU access timing and source-bank visibility.
kitaqfc/kitaqfc/CodeGenerator.cs:4817
case "__vram_write": return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,2,1}, funcName);
void EmitHelperVramWrite()
{
EmitHelperStart("__vram_write");
EmitPpuAddrFromCallArg();
EmitAsm("LDY", Imm(0));
string loop = NewGeneratedLabel("vram_write_loop");
string done = NewGeneratedLabel("vram_write_done");
_assembly.Add(Expr.Make(Tag.Label, loop));
EmitAsm("CPY", Mem(CallArgBase + 4));
EmitAsm("BEQ", Rel(done));
EmitAsm("LDA", IndY(CallArgBase + 2));
EmitAsm("STA", Mem(0x2007));
EmitAsm("INY");
EmitAsm("BNE", Rel(loop));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__vramq_capacity
Gibt die Gesamtkapazität des Befehlspuffers unabhängig von seiner aktuellen Belegung zurück.
Syntax
u8 __vramq_capacity(void);Rückgabewert
Ein vorzeichenloses Byte in den hier beschriebenen plattformspezifischen Einheiten. Die Abfrage verändert oder verarbeitet die Warteschlange nicht.
Bedingungen und Grenzen
FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.
Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.
Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.
Anwendungsbeispiel
capacity=__vramq_capacity();
if (capacity!=128) failures++;Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.
Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.
Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:164
Return total encoded command-buffer capacity; this is independent of PPU VRAM capacity.
__vramq_clear
Verwirft alle ausstehenden Einträge und setzt Länge sowie Bereit-Flag auf 0. Schreibt nicht ins VRAM und löscht die Überlaufmeldung nicht. Rufen Sie dafür gesondert __vramq_clear_overflow auf.
Syntax
void __vramq_clear(void);Rückgabewert
Keiner (void).
Bedingungen und Grenzen
FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.
Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.
Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.
Anwendungsbeispiel
__vramq_clear();
if (__vramq_len()!=0 || __vramq_overflow()!=1) failures++;
// Clearing queued work deliberately leaves the overflow latch set.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.
Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.
Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:146
NMI VRAM transfer queue. Queue entries are consumed by __vramq_exec(), and the default NMI handler calls __vramq_exec() automatically. Reset pending intrinsic-queue records; this is separate from runtime.c nes_vram_queue_clear.
__vramq_clear_overflow
Setzt nur die Überlaufmeldung auf 0. Einträge, Länge und Bereitschaft bleiben unverändert. Bestätigt den Platzmangel, ohne den abgelehnten Eintrag erneut zu versuchen oder frühere erfolgreiche Einträge zurückzunehmen.
Syntax
void __vramq_clear_overflow(void);Rückgabewert
Keiner (void).
Bedingungen und Grenzen
FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.
Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.
Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.
Anwendungsbeispiel
__vramq_clear_overflow();
if (__vramq_overflow()!=0 || __vramq_len()!=4) failures++;
// Resetting the latch deliberately keeps this pending put record.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.
Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.
Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:162
Clear the overflow latch explicitly after handling the failed submission.
__vramq_commit
Setzt das Bereit-Flag auf 1 und erlaubt damit die Verarbeitung. Kopiert keine Nutzdaten, schreibt nicht in die PPU und wartet nicht auf NMI. Halten Sie Warteschlange und gespeicherte Quellen nach der Freigabe bis zum Abschluss unverändert. Bei aktiviertem NMI kann der Standardhandler sie asynchron verarbeiten.
Syntax
void __vramq_commit(void);Rückgabewert
Keiner (void).
Bedingungen und Grenzen
FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.
Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.
Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.
Anwendungsbeispiel
__vramq_commit(); // NMI is disabled here: this only marks the queue ready.
if (__vramq_len()!=30) failures++;Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.
Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.
Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:154
Publish the pending records to the configured consumer. Do not mutate them while consumption can run.
__vramq_copy
Fügt einen Sechs-Byte-Eintrag an, der bei der Ausführung len Bytes von src nach ppu_addr kopiert. Gespeichert wird der Zeiger, nicht der Inhalt. Selbst eine Kopie von 255 Bytes belegt nur sechs Pufferbytes. Die Quelle wird aufsteigend gelesen; für ein fortlaufendes Ziel wählen Sie PPU-Schrittweite 1.
Syntax
void __vramq_copy(u16 ppu_addr, const u8* src, u8 len);Parameter
ppu_addrPPU-Byteadresse (
0x0000–0x3FFF), kein CPU-Zeiger. Das Ziel muss beschreibbar sein: Musteränderungen benötigen CHR-RAM statt CHR-ROM. PPU-Adressabbildung und Spiegelung gelten weiterhin. Wählen Sie für fortlaufendes Kopieren und Füllen die Schrittweite 1 in PPUCTRL. Führen Sie Befehle zu einem sicheren PPU-Zeitpunkt aus und stellen Sie danach den Scrollzustand her; der Ausführer schreibt PPUADDR und PPUDATA, ohne Scrolleinstellungen wiederherzustellen.srcNahzeiger auf lesbare Bytes, der bis zur Ausführung gespeichert bleibt. Daten und ihre Speicher- oder ROM-Bank müssen während der gesamten Verarbeitung gültig und eingeblendet bleiben; weder Banknummer noch Kopie werden gespeichert. Nichtleere Übertragungen benötigen eine gültige Quelle. Verwenden Sie kein abgelaufenes lokales Array.
lenByteanzahl von 0 bis 255. Ein Eintrag verarbeitet die gesamte Anzahl ohne Aufteilung in 127-Byte-Abschnitte. Auch null belegt einen Kopier- oder Fülleintrag. Bei dessen Ausführung wird die PPU-Adresse gesetzt, aber kein Byte nach PPUDATA geschrieben. 256 liegt außerhalb des Argumentbereichs.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Das Einfügen liefert keinen Erfolgswert. Passt der vollständige Eintrag nicht, wird __vramq_overflow() auf 1 gesetzt; vorhandene Einträge und Länge bleiben erhalten. Genau passender Platz wird akzeptiert. Eine frühere Überlaufmeldung verhindert keinen passenden Eintrag; behandeln oder löschen Sie sie vor der Bewertung eines neuen Stapels. Mehrere Einfügevorgänge bilden keine Transaktion.
FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.
Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.
Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.
Anwendungsbeispiel
__vramq_copy(0x2040,source_bytes,255);
source_bytes[0]=3; // The executor must see this change through its retained pointer.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.
Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.
Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:150
Retain the source address for later consumption; keep its storage and bank mapping valid.
__vramq_exec
Verarbeitet eine freigegebene Warteschlange sofort in Eintragsreihenfolge. Ohne Bereitschaft kehrt die Funktion unverändert zurück. Danach löscht sie Länge und Bereitschaft, behält aber die Überlaufmeldung. Sie wartet nicht auf sichere PPU-Zeitfenster, begrenzt keine Transfermenge und stellt den Scrollzustand nicht wieder her; das übernimmt der Aufrufer.
Syntax
void __vramq_exec(void);Rückgabewert
Keiner (void).
Bedingungen und Grenzen
PPU-Byteadresse (0x0000–0x3FFF), kein CPU-Zeiger. Das Ziel muss beschreibbar sein: Musteränderungen benötigen CHR-RAM statt CHR-ROM. PPU-Adressabbildung und Spiegelung gelten weiterhin. Wählen Sie für fortlaufendes Kopieren und Füllen die Schrittweite 1 in PPUCTRL. Führen Sie Befehle zu einem sicheren PPU-Zeitpunkt aus und stellen Sie danach den Scrollzustand her; der Ausführer schreibt PPUADDR und PPUDATA, ohne Scrolleinstellungen wiederherzustellen.
FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.
Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.
Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.
Anwendungsbeispiel
// Rendering is stopped and the PPU increment is 1.
__vramq_exec();
after_exec=__vramq_len();
if (after_exec!=0 || __vramq_overflow()!=0) failures++;Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.
Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.
Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:156
Execute a ready queue directly; committing and safe PPU timing are caller responsibilities.
__vramq_fill
Fügt einen Fünf-Byte-Eintrag an, der das Byte value bei len PPU-Schreibzugriffen wiederholt. Der Wert wird gespeichert; spätere Änderungen der Aufrufervariablen ändern den eingereihten Füllwert nicht. Wiederholt wird ein Byte, kein mehrteiliges Kachelmuster.
Syntax
void __vramq_fill(u16 ppu_addr, u8 value, u8 len);Parameter
ppu_addrPPU-Byteadresse (
0x0000–0x3FFF), kein CPU-Zeiger. Das Ziel muss beschreibbar sein: Musteränderungen benötigen CHR-RAM statt CHR-ROM. PPU-Adressabbildung und Spiegelung gelten weiterhin. Wählen Sie für fortlaufendes Kopieren und Füllen die Schrittweite 1 in PPUCTRL. Führen Sie Befehle zu einem sicheren PPU-Zeitpunkt aus und stellen Sie danach den Scrollzustand her; der Ausführer schreibt PPUADDR und PPUDATA, ohne Scrolleinstellungen wiederherzustellen.valueEin wiederholtes Byte von 0 bis 255. Seine sichtbare Wirkung hängt davon ab, ob das Ziel Tile-Indizes, Pixelebenen oder eine andere Bytestruktur enthält.
lenByteanzahl von 0 bis 255. Ein Eintrag verarbeitet die gesamte Anzahl ohne Aufteilung in 127-Byte-Abschnitte. Auch null belegt einen Kopier- oder Fülleintrag. Bei dessen Ausführung wird die PPU-Adresse gesetzt, aber kein Byte nach PPUDATA geschrieben. 256 liegt außerhalb des Argumentbereichs.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Das Einfügen liefert keinen Erfolgswert. Passt der vollständige Eintrag nicht, wird __vramq_overflow() auf 1 gesetzt; vorhandene Einträge und Länge bleiben erhalten. Genau passender Platz wird akzeptiert. Eine frühere Überlaufmeldung verhindert keinen passenden Eintrag; behandeln oder löschen Sie sie vor der Bewertung eines neuen Stapels. Mehrere Einfügevorgänge bilden keine Transaktion.
FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.
Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.
Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.
Anwendungsbeispiel
__vramq_fill(0x2160,fill_value,255); // Seven complete rows and 31 tiles in the eighth.
fill_value=3; // The queued value stays 1, so the green block remains solid.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.
Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.
Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:152
Append a repeated-byte update; the source value is stored in the record.
__vramq_len
Gibt den von ausstehenden Befehlen belegten Pufferumfang zurück, nicht die Bytezahl der späteren Videodaten.
Syntax
u8 __vramq_len(void);Rückgabewert
Ein vorzeichenloses Byte in den hier beschriebenen plattformspezifischen Einheiten. Die Abfrage verändert oder verarbeitet die Warteschlange nicht.
Bedingungen und Grenzen
FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.
Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.
Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.
Anwendungsbeispiel
encoded=__vramq_len();
if (encoded!=30 || capacity-encoded!=98) failures++;
// 4+6+5+6+5+4 encoded bytes describe 512 actual byte writes.Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.
Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.
Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:158
Return encoded bytes currently occupied, not transfer payload bytes or command count.
__vramq_overflow
Liest das Überlaufflag, ohne es zu löschen; es kann einen früheren Fehler festhalten.
Syntax
u8 __vramq_overflow(void);Rückgabewert
0 ohne gespeicherten Überlauf; nach einem abgelehnten Eintrag 1 bis zum ausdrücklichen Zurücksetzen. Die Ausführung der Warteschlange löscht diese Meldung nicht.
Bedingungen und Grenzen
FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.
Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.
Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.
Anwendungsbeispiel
overflow_seen=__vramq_overflow();
if (overflow_seen!=1 || __vramq_overflow()!=1) failures++;Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.
Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.
Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:160
Read the latched capacity failure; reading does not reset it.
__vramq_put
Fügt eine PPU-Adresse und einen Bytewert als Vier-Byte-Eintrag an. Der Wert wird sofort in die Warteschlange kopiert und erst bei deren Ausführung geschrieben. Dies eignet sich etwa für eine Nametable-Kachel oder ein anderes beschreibbares PPU-Byte.
Syntax
void __vramq_put(u16 ppu_addr, u8 value);Parameter
ppu_addrPPU-Byteadresse (
0x0000–0x3FFF), kein CPU-Zeiger. Das Ziel muss beschreibbar sein: Musteränderungen benötigen CHR-RAM statt CHR-ROM. PPU-Adressabbildung und Spiegelung gelten weiterhin. Wählen Sie für fortlaufendes Kopieren und Füllen die Schrittweite 1 in PPUCTRL. Führen Sie Befehle zu einem sicheren PPU-Zeitpunkt aus und stellen Sie danach den Scrollzustand her; der Ausführer schreibt PPUADDR und PPUDATA, ohne Scrolleinstellungen wiederherzustellen.valueEin wiederholtes Byte von 0 bis 255. Seine sichtbare Wirkung hängt davon ab, ob das Ziel Tile-Indizes, Pixelebenen oder eine andere Bytestruktur enthält.
Rückgabewert
Keiner (void).
Bedingungen und Grenzen
Das Einfügen liefert keinen Erfolgswert. Passt der vollständige Eintrag nicht, wird __vramq_overflow() auf 1 gesetzt; vorhandene Einträge und Länge bleiben erhalten. Genau passender Platz wird akzeptiert. Eine frühere Überlaufmeldung verhindert keinen passenden Eintrag; behandeln oder löschen Sie sie vor der Bewertung eines neuen Stapels. Mehrere Einfügevorgänge bilden keine Transaktion.
FC zählt codierte Befehlsbytes in einem festen intrinsischen 128-Byte-Puffer. Ein Tile-Schreibbefehl braucht 4 Bytes, ein Fülleintrag 5 und eine Zeigerkopie 6 einschließlich Metadaten. NMI kann eine bereite Warteschlange verarbeiten; prüfen und befüllen Sie sie vor der Freigabe und koordinieren Sie Erzeuger und Verbraucher.
Diese Intrinsics verwenden denselben Puffer wie lib/vram.c. Die Funktionen nes_vram_queue_* in runtime.c verwalten eine getrennte Warteschlange mit anderem Eintragsformat. Leeren oder Abfragen der einen betrifft die andere nicht.
Koordinieren Sie Aufbau und Verarbeitung der Warteschlange. Fügen Sie während ihrer Interrupt-Ausführung nichts hinzu, ändern Sie die Verwaltung nicht direkt und überschreiben Sie gespeicherte Quelldaten nicht versehentlich. Diese APIs bieten weder Sperren noch Doppelpufferung. Die Kapazität betrifft den Befehlspuffer, nicht freie Bytes im Hardware-VRAM.
Anwendungsbeispiel
__vramq_put(0x2282,1); // Red 8x8 sentinel at pixel (16,160).Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Das Programm zeigt Kapazität, gespeicherte Zeiger, getrennte Rücksetzfunktionen sowie direkte und NMI-Ausführung. Erwartet werden CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 und FAILED CHECKS 000. Sechs Einträge belegen 30 Bytes und führen 512 Byte-Schreibzugriffe aus. OVERFLOW SEEN speichert die frühere absichtliche Ablehnung; es bedeutet nicht, dass am Ende noch ein Überlauf gemeldet wird.
Die blaue Kopie beginnt bei Pixel (0,16), die grüne Füllung bei (0,88). Jeder Bereich umfasst sieben vollständige Kachelzeilen mit 256 Pixeln Breite und 248 Pixel der achten: insgesamt 255 Kacheln. Das letzte Feld von 8 × 8 bleibt schwarz. Die Kopie wiederholt volle, links halbvolle und dreieckige Muster; das erste ist ein Dreieck, weil die Quelle nach dem Einreihen geändert wird. Die Füllung bleibt trotz geänderter Aufrufervariable voll. Form und Farbe werden geprüft.
Bei y=160 werden ein volles rotes Quadrat bei x=16, eine rote linke Halbkachel bei x=224 und ein volles rotes Quadrat bei x=240 erwartet. Das erste übersteht Einträge mit Länge null, das mittlere belegt die NMI-Verarbeitung, das letzte einen einzelnen Put. Das verworfene Feld bei (248,160) bleibt schwarz. Lange Transfers laufen bei abgeschaltetem Rendering und NMI; nur der letzte kleine Schreibzugriff nutzt NMI. Dies ist ein NROM-Emulatortest, kein Test auf echter Hardware.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\vramq_intrinsics.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\vramq_intrinsics.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\api-examples\fc\vram_shapes.chr --no-cache --no-disasmImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:148
Append one address/value update; use the overflow latch to detect capacity failure.
__xy_in_rect
Prüft die Zugehörigkeit zu einem Rechteck einschließlich oberer/linker und ausschließlich unterer/rechter Kante.
Syntax
u8 __xy_in_rect(u8 x, u8 y, u8 rx, u8 ry, u8 rw, u8 rh);Parameter
xPunkt-X, 0..255.
yPunkt-Y, 0..255.
rxLinke Rechteckkante, 0..255.
ryObere Rechteckkante, 0..255.
rwBreite, 0..255.
rhHöhe, 0..255.
Rückgabewert
Gibt innerhalb 1 zurück, sonst 0. Bei Breite oder Höhe null ist das Rechteck leer.
Bedingungen und Grenzen
Verwendet Koordinatendifferenzen: Eine rechte Grenze über 255 läuft nicht nach links um.
Anwendungsbeispiel
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);Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
Prüfen Sie Innenraum, Grenzen, einen äußeren Punkt und Breite null: INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1. Prüfen Sie Distanz 15, umgelaufene Distanz 254 und Array-Indizes 64/65280.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\batch3_coordinates.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\batch3_coordinates.nes --no-cache --no-disasm --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chrImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:31
Return 0 or 1 for half-open bounds. Zero extents are empty; coordinates do not wrap across 255.
kitaqfc/kitaqfc/CodeGenerator.cs:4627
case "__xy_in_rect":
return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {1,1,1,1,1,1}, "__xy_in_rect");
void EmitHelperGeometryAndRng()
{
EmitHelperStart("__xy_in_rect");
string xyFalse = NewGeneratedLabel("xy_in_rect_false");
string xyDone = NewGeneratedLabel("xy_in_rect_done");
// return (x-rx < rw) && (y-ry < rh), using unsigned subtract to avoid rx+rw overflow.
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 2));
EmitAsm("BCC", Rel(xyFalse));
EmitAsm("CMP", Mem(CallArgBase + 4));
EmitAsm("BCS", Rel(xyFalse));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 3));
EmitAsm("BCC", Rel(xyFalse));
EmitAsm("CMP", Mem(CallArgBase + 5));
EmitAsm("BCS", Rel(xyFalse));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(xyDone));
_assembly.Add(Expr.Make(Tag.Label, xyFalse));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, xyDone));
EmitAsm("RTS");
EmitHelperStart("__manhattan");
// dx = abs(x1-x2)
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 2));
string mdxOk = NewGeneratedLabel("manhattan_dx_ok");
EmitAsm("BCS", Rel(mdxOk));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("CLC");
EmitAsm("ADC", Imm(1));
_assembly.Add(Expr.Make(Tag.Label, mdxOk));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
// dy = abs(y1-y2), then add dx.
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("SEC");
EmitAsm("SBC", Mem(CallArgBase + 3));
string mdyOk = NewGeneratedLabel("manhattan_dy_ok");
EmitAsm("BCS", Rel(mdyOk));
EmitAsm("EOR", Imm(0xFF));
EmitAsm("CLC");
EmitAsm("ADC", Imm(1));
_assembly.Add(Expr.Make(Tag.Label, mdyOk));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Save X-coordinate separately while multiplying width by Y, then add X with carry into the high byte.
EmitHelperStart("__map_index");
// args: x, y, width. Return y * width + x as u16.
EmitAsm("LDA", Mem(CallArgBase)); // x
EmitAsm("STA", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("LDA", Mem(CallArgBase + 1)); // y
EmitAsm("STA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("LDA", Mem(CallArgBase + 2)); // width as u16 multiplicand
EmitAsm("STA", Mem(CallArgBase));
EmitAsm("LDA", Imm(0));
EmitAsm("STA", Mem(CallArgBase + 1));
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp1Address));
EmitAsm("STA", Mem(CallArgBase + 2));
EmitAsm("JSR", Abs("__mul16x8"));
EmitAsm("CLC");
EmitAsm("ADC", Mem(_runtimeIntrinsicTmp2Address));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("TXA");
EmitAsm("ADC", Imm(0));
EmitAsm("TAX");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("RTS");
// Replace both bytes of the persistent generator state with the supplied seed.
EmitHelperStart("__rng_seed");
EmitAsm("LDA", Mem(CallArgBase));
EmitAsm("STA", Mem(_runtimeRngLoAddress));
EmitAsm("LDA", Mem(CallArgBase + 1));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
EmitAsm("RTS");
EmitHelperStart("__rng8");
// 16-bit Galois LFSR: if low bit set, shift right then xor $B400.
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("ORA", Mem(_runtimeRngHiAddress));
// Replace the all-zero LFSR state with a deterministic nonzero seed before advancing.
string seeded = NewGeneratedLabel("rng_seeded");
EmitAsm("BNE", Rel(seeded));
EmitAsm("LDA", Imm(0x5A));
EmitAsm("STA", Mem(_runtimeRngLoAddress));
EmitAsm("LDA", Imm(0xA5));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
_assembly.Add(Expr.Make(Tag.Label, seeded));
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("AND", Imm(1));
EmitAsm("STA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("LSR", Mem(_runtimeRngHiAddress));
EmitAsm("ROR", Mem(_runtimeRngLoAddress));
string rngNoXor = NewGeneratedLabel("rng_no_xor");
EmitAsm("LDA", Mem(_runtimeIntrinsicTmp0Address));
EmitAsm("BEQ", Rel(rngNoXor));
EmitAsm("LDA", Mem(_runtimeRngHiAddress));
EmitAsm("EOR", Imm(0xB4));
EmitAsm("STA", Mem(_runtimeRngHiAddress));
_assembly.Add(Expr.Make(Tag.Label, rngNoXor));
EmitAsm("LDA", Mem(_runtimeRngLoAddress));
EmitAsm("EOR", Mem(_runtimeRngHiAddress));
EmitAsm("RTS");
}__zapper_light
Standardlesefunktion für die Lichterkennung an Port 2. Ruft dieselbe Hilfsfunktion wie __zapper_light2() auf.
Syntax
u8 __zapper_light(void);Rückgabewert
1, wenn Bit 3 von $4017 gelöscht ist, andernfalls 0. Port 1 wird nicht gelesen.
Bedingungen und Grenzen
Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.
Liest das momentane Signal. Erzeugt keinen Strobe, wartet auf keinen Frame, erkennt keine Flanke, bestimmt keinen Zielpunkt und entscheidet keinen Treffer. Rohwert- und Abzugs-/Lichtaufrufe lesen das Register zu ihren jeweiligen Zeitpunkten und können bei wechselndem Signal unterschiedliche Werte liefern.
Der Lichteingang ist aktiv-low: Ein gelöschtes Bit 3 ergibt den Rückgabewert eins. Diese Umwandlung erkennt nicht, ob eine Lichtpistole angeschlossen ist; auch ein Gamepad oder offener Eingang kann eins liefern. Ein gezeichnetes Bildschirmziel macht daraus keine Trefferprüfungs-API.
Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.
Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.
Anwendungsbeispiel
#include "fc_ports_scene.h"
void main(void){
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__zapper_raw1();port_result[1]=__zapper_trigger1();port_result[2]=__zapper_light1();
port_result[3]=__zapper_raw2();port_result[4]=__zapper_trigger2();port_result[5]=__zapper_light2();
port_result[6]=__zapper_trigger();port_result[7]=__zapper_light();
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"LIGHT-GUN INPUT SNAPSHOT");m_wait();
m_text(2,5,"PORT RAW TRIGGER LIGHT");m_wait();
m_put(3,8,'1');port_hex(9,8,port_result[0]);port_number(15,8,port_result[1]);port_number(24,8,port_result[2]);m_wait();
m_put(3,10,'2');port_hex(9,10,port_result[3]);port_number(15,10,port_result[4]);port_number(24,10,port_result[5]);m_wait();
m_text(2,13,"DEFAULT = PORT 2");m_wait();
m_text(2,15,"TRIGGER");port_number(15,15,port_result[6]);m_wait();
m_text(2,17,"LIGHT");port_number(15,17,port_result[7]);m_wait();
m_text(2,21,"LIGHT 1 IS NOT A HIT TEST");m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_zapper.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_zapper.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_zapper.nes --frames 120 --png .\out\peripheral_zapper.pngImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:253
Default to the port-2 normalized light reader.
kitaqfc/kitaqfc/CodeGenerator.cs:7753
EmitHelperStart("__zapper_light2");
EmitNormalizeBoolFromMask(0x4017, 0x08, true);
void EmitNormalizeBoolFromMask(int port, int mask, bool invert)
{
string zero = NewGeneratedLabel("bool_zero");
string done = NewGeneratedLabel("bool_done");
EmitAsm("LDA", Mem(port));
EmitAsm("AND", Imm(mask));
if (invert) EmitAsm("BNE", Rel(zero)); else EmitAsm("BEQ", Rel(zero));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(done));
_assembly.Add(Expr.Make(Tag.Label, zero));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__zapper_light1
Liest das Lichtsignal von Port 1 ($4016) und wandelt den Low-aktiven Eingang so um, dass erkanntes Licht den Wert 1 ergibt.
Syntax
u8 __zapper_light1(void);Rückgabewert
1, wenn Bit 3 von $4016 gelöscht ist; 0, wenn es gesetzt ist.
Bedingungen und Grenzen
Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.
Liest das momentane Signal. Erzeugt keinen Strobe, wartet auf keinen Frame, erkennt keine Flanke, bestimmt keinen Zielpunkt und entscheidet keinen Treffer. Rohwert- und Abzugs-/Lichtaufrufe lesen das Register zu ihren jeweiligen Zeitpunkten und können bei wechselndem Signal unterschiedliche Werte liefern.
Der Lichteingang ist aktiv-low: Ein gelöschtes Bit 3 ergibt den Rückgabewert eins. Diese Umwandlung erkennt nicht, ob eine Lichtpistole angeschlossen ist; auch ein Gamepad oder offener Eingang kann eins liefern. Ein gezeichnetes Bildschirmziel macht daraus keine Trefferprüfungs-API.
Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.
Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.
Anwendungsbeispiel
#include "fc_ports_scene.h"
void main(void){
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__zapper_raw1();port_result[1]=__zapper_trigger1();port_result[2]=__zapper_light1();
port_result[3]=__zapper_raw2();port_result[4]=__zapper_trigger2();port_result[5]=__zapper_light2();
port_result[6]=__zapper_trigger();port_result[7]=__zapper_light();
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"LIGHT-GUN INPUT SNAPSHOT");m_wait();
m_text(2,5,"PORT RAW TRIGGER LIGHT");m_wait();
m_put(3,8,'1');port_hex(9,8,port_result[0]);port_number(15,8,port_result[1]);port_number(24,8,port_result[2]);m_wait();
m_put(3,10,'2');port_hex(9,10,port_result[3]);port_number(15,10,port_result[4]);port_number(24,10,port_result[5]);m_wait();
m_text(2,13,"DEFAULT = PORT 2");m_wait();
m_text(2,15,"TRIGGER");port_number(15,15,port_result[6]);m_wait();
m_text(2,17,"LIGHT");port_number(15,17,port_result[7]);m_wait();
m_text(2,21,"LIGHT 1 IS NOT A HIT TEST");m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_zapper.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_zapper.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_zapper.nes --frames 120 --png .\out\peripheral_zapper.pngImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:247
Invert port-1 bit 3 and normalize: one means light detected.
kitaqfc/kitaqfc/CodeGenerator.cs:7751
EmitHelperStart("__zapper_light1");
EmitNormalizeBoolFromMask(0x4016, 0x08, true);
void EmitNormalizeBoolFromMask(int port, int mask, bool invert)
{
string zero = NewGeneratedLabel("bool_zero");
string done = NewGeneratedLabel("bool_done");
EmitAsm("LDA", Mem(port));
EmitAsm("AND", Imm(mask));
if (invert) EmitAsm("BNE", Rel(zero)); else EmitAsm("BEQ", Rel(zero));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(done));
_assembly.Add(Expr.Make(Tag.Label, zero));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__zapper_light2
Liest das Lichtsignal von Port 2 ($4017) und wandelt den Low-aktiven Eingang so um, dass erkanntes Licht den Wert 1 ergibt.
Syntax
u8 __zapper_light2(void);Rückgabewert
1, wenn Bit 3 von $4017 gelöscht ist; 0, wenn es gesetzt ist.
Bedingungen und Grenzen
Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.
Liest das momentane Signal. Erzeugt keinen Strobe, wartet auf keinen Frame, erkennt keine Flanke, bestimmt keinen Zielpunkt und entscheidet keinen Treffer. Rohwert- und Abzugs-/Lichtaufrufe lesen das Register zu ihren jeweiligen Zeitpunkten und können bei wechselndem Signal unterschiedliche Werte liefern.
Der Lichteingang ist aktiv-low: Ein gelöschtes Bit 3 ergibt den Rückgabewert eins. Diese Umwandlung erkennt nicht, ob eine Lichtpistole angeschlossen ist; auch ein Gamepad oder offener Eingang kann eins liefern. Ein gezeichnetes Bildschirmziel macht daraus keine Trefferprüfungs-API.
Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.
Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.
Anwendungsbeispiel
#include "fc_ports_scene.h"
void main(void){
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__zapper_raw1();port_result[1]=__zapper_trigger1();port_result[2]=__zapper_light1();
port_result[3]=__zapper_raw2();port_result[4]=__zapper_trigger2();port_result[5]=__zapper_light2();
port_result[6]=__zapper_trigger();port_result[7]=__zapper_light();
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"LIGHT-GUN INPUT SNAPSHOT");m_wait();
m_text(2,5,"PORT RAW TRIGGER LIGHT");m_wait();
m_put(3,8,'1');port_hex(9,8,port_result[0]);port_number(15,8,port_result[1]);port_number(24,8,port_result[2]);m_wait();
m_put(3,10,'2');port_hex(9,10,port_result[3]);port_number(15,10,port_result[4]);port_number(24,10,port_result[5]);m_wait();
m_text(2,13,"DEFAULT = PORT 2");m_wait();
m_text(2,15,"TRIGGER");port_number(15,15,port_result[6]);m_wait();
m_text(2,17,"LIGHT");port_number(15,17,port_result[7]);m_wait();
m_text(2,21,"LIGHT 1 IS NOT A HIT TEST");m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_zapper.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_zapper.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_zapper.nes --frames 120 --png .\out\peripheral_zapper.pngImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:249
Invert port-2 bit 3 and normalize: one means light detected.
kitaqfc/kitaqfc/CodeGenerator.cs:7753
EmitHelperStart("__zapper_light2");
EmitNormalizeBoolFromMask(0x4017, 0x08, true);
void EmitNormalizeBoolFromMask(int port, int mask, bool invert)
{
string zero = NewGeneratedLabel("bool_zero");
string done = NewGeneratedLabel("bool_done");
EmitAsm("LDA", Mem(port));
EmitAsm("AND", Imm(mask));
if (invert) EmitAsm("BNE", Rel(zero)); else EmitAsm("BEQ", Rel(zero));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(done));
_assembly.Add(Expr.Make(Tag.Label, zero));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__zapper_raw1
Liest beide Lightgun-Bits von Port 1 ($4016) gemeinsam, wenn Abzug und Lichterkennung aus demselben Registerlesezugriff ausgewertet werden sollen.
Syntax
u8 __zapper_raw1(void);Rückgabewert
Liefert $4016 & 0x18: 0x00, 0x08, 0x10 oder 0x18. Bit 4 ist das Abzugssignal; Bit 3 ist das rohe Signal für fehlendes Licht.
Bedingungen und Grenzen
Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.
Liest das momentane Signal. Erzeugt keinen Strobe, wartet auf keinen Frame, erkennt keine Flanke, bestimmt keinen Zielpunkt und entscheidet keinen Treffer. Rohwert- und Abzugs-/Lichtaufrufe lesen das Register zu ihren jeweiligen Zeitpunkten und können bei wechselndem Signal unterschiedliche Werte liefern.
Der Lichteingang ist aktiv-low: Ein gelöschtes Bit 3 ergibt den Rückgabewert eins. Diese Umwandlung erkennt nicht, ob eine Lichtpistole angeschlossen ist; auch ein Gamepad oder offener Eingang kann eins liefern. Ein gezeichnetes Bildschirmziel macht daraus keine Trefferprüfungs-API.
Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.
Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.
Anwendungsbeispiel
#include "fc_ports_scene.h"
void main(void){
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__zapper_raw1();port_result[1]=__zapper_trigger1();port_result[2]=__zapper_light1();
port_result[3]=__zapper_raw2();port_result[4]=__zapper_trigger2();port_result[5]=__zapper_light2();
port_result[6]=__zapper_trigger();port_result[7]=__zapper_light();
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"LIGHT-GUN INPUT SNAPSHOT");m_wait();
m_text(2,5,"PORT RAW TRIGGER LIGHT");m_wait();
m_put(3,8,'1');port_hex(9,8,port_result[0]);port_number(15,8,port_result[1]);port_number(24,8,port_result[2]);m_wait();
m_put(3,10,'2');port_hex(9,10,port_result[3]);port_number(15,10,port_result[4]);port_number(24,10,port_result[5]);m_wait();
m_text(2,13,"DEFAULT = PORT 2");m_wait();
m_text(2,15,"TRIGGER");port_number(15,15,port_result[6]);m_wait();
m_text(2,17,"LIGHT");port_number(15,17,port_result[7]);m_wait();
m_text(2,21,"LIGHT 1 IS NOT A HIT TEST");m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_zapper.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_zapper.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_zapper.nes --frames 120 --png .\out\peripheral_zapper.pngImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:239
Zapper / light gun. The no-suffix names default to the usual port-2/Famicom expansion-port mapping. __zapper_light*() returns 1 when light is detected. Return raw 4016 & 18, retaining trigger and active-low light bits.
kitaqfc/kitaqfc/CodeGenerator.cs:4775
case "__zapper_raw1": if (!Need(0)) return true; EmitAsm("LDA", Mem(0x4016)); EmitAsm("AND", Imm(0x18)); return true;__zapper_raw2
Liest beide Lightgun-Bits von Port 2 ($4017) gemeinsam, wenn Abzug und Lichterkennung aus demselben Registerlesezugriff ausgewertet werden sollen.
Syntax
u8 __zapper_raw2(void);Rückgabewert
Liefert $4017 & 0x18: 0x00, 0x08, 0x10 oder 0x18. Bit 4 ist das Abzugssignal; Bit 3 ist das rohe Signal für fehlendes Licht.
Bedingungen und Grenzen
Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.
Liest das momentane Signal. Erzeugt keinen Strobe, wartet auf keinen Frame, erkennt keine Flanke, bestimmt keinen Zielpunkt und entscheidet keinen Treffer. Rohwert- und Abzugs-/Lichtaufrufe lesen das Register zu ihren jeweiligen Zeitpunkten und können bei wechselndem Signal unterschiedliche Werte liefern.
Der Lichteingang ist aktiv-low: Ein gelöschtes Bit 3 ergibt den Rückgabewert eins. Diese Umwandlung erkennt nicht, ob eine Lichtpistole angeschlossen ist; auch ein Gamepad oder offener Eingang kann eins liefern. Ein gezeichnetes Bildschirmziel macht daraus keine Trefferprüfungs-API.
Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.
Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.
Anwendungsbeispiel
#include "fc_ports_scene.h"
void main(void){
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__zapper_raw1();port_result[1]=__zapper_trigger1();port_result[2]=__zapper_light1();
port_result[3]=__zapper_raw2();port_result[4]=__zapper_trigger2();port_result[5]=__zapper_light2();
port_result[6]=__zapper_trigger();port_result[7]=__zapper_light();
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"LIGHT-GUN INPUT SNAPSHOT");m_wait();
m_text(2,5,"PORT RAW TRIGGER LIGHT");m_wait();
m_put(3,8,'1');port_hex(9,8,port_result[0]);port_number(15,8,port_result[1]);port_number(24,8,port_result[2]);m_wait();
m_put(3,10,'2');port_hex(9,10,port_result[3]);port_number(15,10,port_result[4]);port_number(24,10,port_result[5]);m_wait();
m_text(2,13,"DEFAULT = PORT 2");m_wait();
m_text(2,15,"TRIGGER");port_number(15,15,port_result[6]);m_wait();
m_text(2,17,"LIGHT");port_number(15,17,port_result[7]);m_wait();
m_text(2,21,"LIGHT 1 IS NOT A HIT TEST");m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_zapper.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_zapper.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_zapper.nes --frames 120 --png .\out\peripheral_zapper.pngImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:241
Return raw 4017 & 18 for the second port.
kitaqfc/kitaqfc/CodeGenerator.cs:4776
case "__zapper_raw2": if (!Need(0)) return true; EmitAsm("LDA", Mem(0x4017)); EmitAsm("AND", Imm(0x18)); return true;__zapper_trigger
Standardlesefunktion für den Abzug an Port 2. Ruft dieselbe Hilfsfunktion wie __zapper_trigger2() auf.
Syntax
u8 __zapper_trigger(void);Rückgabewert
Das auf 0 oder 1 normalisierte Bit 4 von $4017. Es wird nie automatisch auf Port 1 umgeschaltet.
Bedingungen und Grenzen
Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.
Liest das momentane Signal. Erzeugt keinen Strobe, wartet auf keinen Frame, erkennt keine Flanke, bestimmt keinen Zielpunkt und entscheidet keinen Treffer. Rohwert- und Abzugs-/Lichtaufrufe lesen das Register zu ihren jeweiligen Zeitpunkten und können bei wechselndem Signal unterschiedliche Werte liefern.
Das Ergebnis ist der logische Zustand der Abzugsleitung. Es garantiert keinen weiterhin vollständig gezogenen Abzug; das Schalterverhalten hängt vom Gerät ab. Vergleichen Sie für ein Schussereignis den vorherigen Messwert und erkennen Sie den gewünschten Übergang.
Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.
Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.
Anwendungsbeispiel
#include "fc_ports_scene.h"
void main(void){
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__zapper_raw1();port_result[1]=__zapper_trigger1();port_result[2]=__zapper_light1();
port_result[3]=__zapper_raw2();port_result[4]=__zapper_trigger2();port_result[5]=__zapper_light2();
port_result[6]=__zapper_trigger();port_result[7]=__zapper_light();
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"LIGHT-GUN INPUT SNAPSHOT");m_wait();
m_text(2,5,"PORT RAW TRIGGER LIGHT");m_wait();
m_put(3,8,'1');port_hex(9,8,port_result[0]);port_number(15,8,port_result[1]);port_number(24,8,port_result[2]);m_wait();
m_put(3,10,'2');port_hex(9,10,port_result[3]);port_number(15,10,port_result[4]);port_number(24,10,port_result[5]);m_wait();
m_text(2,13,"DEFAULT = PORT 2");m_wait();
m_text(2,15,"TRIGGER");port_number(15,15,port_result[6]);m_wait();
m_text(2,17,"LIGHT");port_number(15,17,port_result[7]);m_wait();
m_text(2,21,"LIGHT 1 IS NOT A HIT TEST");m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_zapper.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_zapper.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_zapper.nes --frames 120 --png .\out\peripheral_zapper.pngImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:251
Default to the port-2 normalized trigger reader.
kitaqfc/kitaqfc/CodeGenerator.cs:7749
EmitHelperStart("__zapper_trigger2");
EmitNormalizeBoolFromMask(0x4017, 0x10, false);
void EmitNormalizeBoolFromMask(int port, int mask, bool invert)
{
string zero = NewGeneratedLabel("bool_zero");
string done = NewGeneratedLabel("bool_done");
EmitAsm("LDA", Mem(port));
EmitAsm("AND", Imm(mask));
if (invert) EmitAsm("BNE", Rel(zero)); else EmitAsm("BEQ", Rel(zero));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(done));
_assembly.Add(Expr.Make(Tag.Label, zero));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__zapper_trigger1
Liest das Abzugssignal von Port 1 ($4016) und normalisiert es auf 0 oder 1.
Syntax
u8 __zapper_trigger1(void);Rückgabewert
1, wenn Bit 4 von $4016 gesetzt ist, andernfalls 0.
Bedingungen und Grenzen
Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.
Liest das momentane Signal. Erzeugt keinen Strobe, wartet auf keinen Frame, erkennt keine Flanke, bestimmt keinen Zielpunkt und entscheidet keinen Treffer. Rohwert- und Abzugs-/Lichtaufrufe lesen das Register zu ihren jeweiligen Zeitpunkten und können bei wechselndem Signal unterschiedliche Werte liefern.
Das Ergebnis ist der logische Zustand der Abzugsleitung. Es garantiert keinen weiterhin vollständig gezogenen Abzug; das Schalterverhalten hängt vom Gerät ab. Vergleichen Sie für ein Schussereignis den vorherigen Messwert und erkennen Sie den gewünschten Übergang.
Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.
Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.
Anwendungsbeispiel
#include "fc_ports_scene.h"
void main(void){
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__zapper_raw1();port_result[1]=__zapper_trigger1();port_result[2]=__zapper_light1();
port_result[3]=__zapper_raw2();port_result[4]=__zapper_trigger2();port_result[5]=__zapper_light2();
port_result[6]=__zapper_trigger();port_result[7]=__zapper_light();
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"LIGHT-GUN INPUT SNAPSHOT");m_wait();
m_text(2,5,"PORT RAW TRIGGER LIGHT");m_wait();
m_put(3,8,'1');port_hex(9,8,port_result[0]);port_number(15,8,port_result[1]);port_number(24,8,port_result[2]);m_wait();
m_put(3,10,'2');port_hex(9,10,port_result[3]);port_number(15,10,port_result[4]);port_number(24,10,port_result[5]);m_wait();
m_text(2,13,"DEFAULT = PORT 2");m_wait();
m_text(2,15,"TRIGGER");port_number(15,15,port_result[6]);m_wait();
m_text(2,17,"LIGHT");port_number(15,17,port_result[7]);m_wait();
m_text(2,21,"LIGHT 1 IS NOT A HIT TEST");m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_zapper.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_zapper.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_zapper.nes --frames 120 --png .\out\peripheral_zapper.pngImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:243
Return port-1 trigger bit 4 normalized to 0 or 1.
kitaqfc/kitaqfc/CodeGenerator.cs:7747
EmitHelperStart("__zapper_trigger1");
EmitNormalizeBoolFromMask(0x4016, 0x10, false);
void EmitNormalizeBoolFromMask(int port, int mask, bool invert)
{
string zero = NewGeneratedLabel("bool_zero");
string done = NewGeneratedLabel("bool_done");
EmitAsm("LDA", Mem(port));
EmitAsm("AND", Imm(mask));
if (invert) EmitAsm("BNE", Rel(zero)); else EmitAsm("BEQ", Rel(zero));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(done));
_assembly.Add(Expr.Make(Tag.Label, zero));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}__zapper_trigger2
Liest das Abzugssignal von Port 2 ($4017) und normalisiert es auf 0 oder 1.
Syntax
u8 __zapper_trigger2(void);Rückgabewert
1, wenn Bit 4 von $4017 gesetzt ist, andernfalls 0.
Bedingungen und Grenzen
Diese APIs greifen direkt auf $4016/$4017 zu. Verschachteln Sie darauf keine interruptgesteuerten Gamepad-, Tastatur- oder seriellen Vorgänge. Auch die Schiebepositionen der Controller können betroffen sein. Das Beispiel deaktiviert NMI während der Eingabeaufnahme und aktiviert sie bei der Anzeigeinitialisierung wieder.
Liest das momentane Signal. Erzeugt keinen Strobe, wartet auf keinen Frame, erkennt keine Flanke, bestimmt keinen Zielpunkt und entscheidet keinen Treffer. Rohwert- und Abzugs-/Lichtaufrufe lesen das Register zu ihren jeweiligen Zeitpunkten und können bei wechselndem Signal unterschiedliche Werte liefern.
Das Ergebnis ist der logische Zustand der Abzugsleitung. Es garantiert keinen weiterhin vollständig gezogenen Abzug; das Schalterverhalten hängt vom Gerät ab. Vergleichen Sie für ein Schussereignis den vorherigen Messwert und erkennen Sie den gewünschten Übergang.
Die Bitumwandlung wird außerdem in einem selbst erstellten Test-ROM geprüft. Dabei werden Registerlesezugriffe auf Testwerte im RAM umgeleitet. Die Tests decken 00/08/10/18 mit zusätzlich gesetzten, hier irrelevanten Bits ab. Dieser Modultest ist getrennt vom unveränderten Beispielprogramm und vom Verhalten echter Peripherie zu betrachten.
Diese Bilder stammen aus unverändert in KUROSAKI ausgeführten herunterladbaren ROMs. Sein Eingabebus bildet Gamepads nach, aber keine Tastatur-, Lichtpistolen- oder seriellen Adaptersignale. Externe Eingabebits werden als null gelesen; die Aufnahmen prüfen diese Geräte daher nicht. Physische Tasteneingaben, optische Erfassung und Kommunikation bleiben ungeprüft.
Anwendungsbeispiel
#include "fc_ports_scene.h"
void main(void){
__irq_disable();__ppu_ctrl_set(0);port_phase=1;
port_result[0]=__zapper_raw1();port_result[1]=__zapper_trigger1();port_result[2]=__zapper_light1();
port_result[3]=__zapper_raw2();port_result[4]=__zapper_trigger2();port_result[5]=__zapper_light2();
port_result[6]=__zapper_trigger();port_result[7]=__zapper_light();
port_phase=2;port_result[31]=165;
m_init();m_text(2,2,"LIGHT-GUN INPUT SNAPSHOT");m_wait();
m_text(2,5,"PORT RAW TRIGGER LIGHT");m_wait();
m_put(3,8,'1');port_hex(9,8,port_result[0]);port_number(15,8,port_result[1]);port_number(24,8,port_result[2]);m_wait();
m_put(3,10,'2');port_hex(9,10,port_result[3]);port_number(15,10,port_result[4]);port_number(24,10,port_result[5]);m_wait();
m_text(2,13,"DEFAULT = PORT 2");m_wait();
m_text(2,15,"TRIGGER");port_number(15,15,port_result[6]);m_wait();
m_text(2,17,"LIGHT");port_number(15,17,port_result[7]);m_wait();
m_text(2,21,"LIGHT 1 IS NOT A HIT TEST");m_wait();
while(1){m_wait();}
}Dieser Ausschnitt zeigt den Aufruf. Beachten Sie beim Einbau in ein vollständiges Programm die angegebenen Anforderungen an Initialisierung und Puffer.
Erwartetes Ergebnis
In KUROSAKI zeigen beide Ports RAW=00, TRIGGER=000 und LIGHT=001. Die Aufrufe ohne Suffix liefern wie Port 2 die Werte 000/001. LIGHT=001 entsteht, weil das nicht emulierte Eingangsbit 3 als null gelesen wird, und nicht durch eine Helligkeitserkennung. Auch auf dem Bildschirm steht LIGHT 1 IS NOT A HIT TEST.

Vollständiges Beispielprogramm
Build-Befehl
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\peripheral_zapper.c -I .\kitaqfc\lib -I .\kitaq-docs\samples -o .\out\peripheral_zapper.nes --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr --no-cache --no-disasm
.\kurosaki\kurosaki.exe run .\out\peripheral_zapper.nes --frames 120 --png .\out\peripheral_zapper.pngImplementierung und Quelltext
kitaqfc/lib/intrinsics.h:245
Return port-2 trigger bit 4 normalized to 0 or 1.
kitaqfc/kitaqfc/CodeGenerator.cs:7749
EmitHelperStart("__zapper_trigger2");
EmitNormalizeBoolFromMask(0x4017, 0x10, false);
void EmitNormalizeBoolFromMask(int port, int mask, bool invert)
{
string zero = NewGeneratedLabel("bool_zero");
string done = NewGeneratedLabel("bool_done");
EmitAsm("LDA", Mem(port));
EmitAsm("AND", Imm(mask));
if (invert) EmitAsm("BNE", Rel(zero)); else EmitAsm("BEQ", Rel(zero));
EmitAsm("LDA", Imm(1));
EmitAsm("JMP", Abs(done));
_assembly.Add(Expr.Make(Tag.Label, zero));
EmitAsm("LDA", Imm(0));
_assembly.Add(Expr.Make(Tag.Label, done));
EmitAsm("RTS");
}Referenz der Befehlssyntax
Die folgende Hilfe wurde für diese Quellenausgabe aufgezeichnet. Sie erläutert optionale Argumente, Standardwerte und zulässige Werte. Ersetzen Sie die beispielhaften Eingabedateinamen durch Ihre eigenen Ausgaben. Aufgezeichnete Werkzeugantworten behalten ihren ursprünglichen Wortlaut.
kitaqfc
KITAQFC native Rust compiler
Build a NES ROM:
kitaqfc SOURCE.c ... -I INCLUDE --mapper=nrom -o GAME.nes
kitaqfc SOURCE.c ... -I lib --mapper=mmc3 --nes-chr=tiles.chr -O2 -o GAME.nes
Build a Famicom Disk System image:
kitaqfc SOURCE.c ... --mapper=fds --fds-layout=fds32 -o GAME.fds
--fds-meta=FILE --fds-meta-out[=FILE] --fds-out[=FILE]
--fds-auto-overlay / --fds-no-auto-overlay
--fds-overlay-farcall / --fds-no-overlay-farcall
--fds-overlay-guard / --fds-no-overlay-guard
--fds-overlay-trim --fds-overlay-prefix=NAME --fds-overlay-start-id=N
--fds-overlay-table / --fds-no-overlay-table
--fds-header / --fds-raw --fds-game-code=CODE
--fds-license-bypass / --fds-no-license-bypass
Cartridge and optimization:
--target=nes|fc|famicom|6502|2a03
--mapper=nrom|uxrom|cnrom|axrom|mmc1|mmc3|mmc5|vrc6|vrc7|fme7|fds|surom512
--board=auto|generic|surom512 --mirroring=horizontal|vertical|four_screen
--battery / --no-battery --nes-chr-ram --nes-chr=FILE
-O0 / -O1 / -O2 --nes-opt=all|off
--abi=legacy|stack|fastcall --reg-cc-v2 / --no-reg-cc-v2
--zp-alloc --static-frame --small-inline --loop-lowering
--library-lto-lite --mapper-aware-placement --kurosaki-profile=FILE
--nes-local-ram=START:LENGTH --nes-temp-ram=START:LENGTH
-Zcheck -Zcheck-bounds -Zconst-scalar-in-rom
Diagnostics and reports:
--strict / --permissive --max-errors=N --diag-json[=FILE|-]
--profile=dev|release|test --cache / --no-cache --deps-out[=FILE]
--debug-output[=DIR] --trace[=tokens,ast,ir,asm] --trace-out=DIR
--no-debug-output --no-trace --fast-build --emit-path-manifest=FILE
--kurosaki-metadata[=FILE] --nes-opt-report[=FILE]
--func-size-report --hotspot-report --cross-bank-report --farcall-suggest
--abi-verify --abi-diff-report --bank-sim --opt-diff --rst-report --repro-check
Report switches accept =FILE; otherwise reports are placed beside the ROM.
Frontend and typed assembly:
tokenize SOURCE.c ... -I INCLUDE -o tokens.json
parse SOURCE.c ... -I INCLUDE -o ast.json
lower SOURCE.c ... -I INCLUDE -o lowered.json
emit-ir SOURCE.c ... -I INCLUDE -o assembly.json
assemble-ir ASSEMBLY.json --mapper=nrom -o ROM.nes
Utilities and development:
kqhelp, symfind, src2asm, romdiff, template, snippet,
conventions, fixhint, irsum, attrviz, recipe, test
SOURCE.c ... --watch [--watch-max-builds=N]
devserver SOURCE.c ... [--once --poll-ms=N --debounce-ms=N --max-builds=N]
SOURCE.c ... --repro-pack[=DIR] --auto-minimize
SOURCE.c ... --minimize[=FILE] --minimize-work=DIR --minimize-out=FILE
Legacy ROM-header and RST flags retain the C# NES backend's behavior.
NES disassembly and managed debugger attachment do not run in that backend.Syntax der Entwicklungsbefehle
Diese Aufrufschemata stammen aus Program.DebugTools.cs und Program.VibeTools.cs. ROM-Vergleich, Symbolsuche und Vorlagen sind eigene Unterbefehle neben der gewöhnlichen C-Kompilierung.
usage: kitaqfc kqhelp <KQxxxx>usage: kitaqfc symfind <pattern> [--map=<.map>] [--dbg=<.dbg>] [--regex] [--ignore-case] [--out=<file>|-]usage: kitaqfc src2asm <source.c:line> [--disasm=<dis.s>] [--funcsizes=<.funcsizes.txt>] [--context=N] [--out=<file>|-]usage: kitaqfc romdiff <old.nes> <new.nes> [--old-func=<.funcsizes.txt>] [--new-func=<.funcsizes.txt>] [--old-map=<.map>] [--new-map=<.map>] [--out=<file>|-]usage: kitaqfc template [out.c] [--out=<file.c>] [--overwrite] [--no-build-script] [--no-readme]usage: kitaqfc conventions [--out=<file>|--stdout]usage: kitaqfc snippet list | get <id> [--out=<file>|-] | emit <category|all> [--out=<file>|-]usage: kitaqfc fixhint [diag.json|stderr.log] [--in=<file>] [--out=<file>|-] [--max=N]usage: kitaqfc irsum [--ir=<syntax_tree_lowered.txt>] [--asm=<assembly_code.txt>] [--top=N] [--out=<file>|-]usage: kitaqfc devserver [--poll-ms=N] [--debounce-ms=N] [--once] [--max-builds=N] [--deps=<file>] <compile args...>usage: kitaqfc recipe [--history=<file>] [--last=N] [--out=<file>] [--script=<file>] [--stdout]Vom Parser erkannte lange Optionen
Die Schreibweisen wurden aus src/cli.rs entnommen, einschließlich der Kompatibilitäts- und Untersuchungsoptionen. Ob eine Option einen Wert erwartet, entnehmen Sie der Hilfe und der zugehörigen Verarbeitungsroutine.
--abi--abi-diff-report--abi-verify--attach--auto-inline-small--auto-minimize--bank-sim--battery--board--cache--cart--cgb--cgb-consistency--chr-rom--container--cross-bank-report--debug-out--debug-output--deps-out--dest--diag-json--disasm--disasm-changed--disasm-changed-out--emit-ai-metadata--farcall-suggest--fast--fast-build--fastcall-v2--fds-approval-bypass--fds-auto-overlay--fds-auto-overlays--fds-builder-manifest--fds-bypass-license--fds-game-code--fds-header--fds-image--fds-layout--fds-license-bypass--fds-manifest--fds-meta--fds-meta-out--fds-metadata--fds-metadata-out--fds-no-approval-bypass--fds-no-auto-overlay--fds-no-auto-overlays--fds-no-header--fds-no-license-bypass--fds-no-overlay-farcall--fds-no-overlay-guard--fds-no-overlay-table--fds-no-residency-guard--fds-out--fds-overlay-farcall--fds-overlay-guard--fds-overlay-id-base--fds-overlay-no-trim--fds-overlay-prefix--fds-overlay-start-id--fds-overlay-table--fds-overlay-trim--fds-raw--fds-residency-guard--func-size-report--help--hotspot-report--include-dir--kurosaki-meta--kurosaki-metadata--kurosaki-out--kurosaki-profile--library-lto-lite--loop-lowering--lto-lite--mapper--mapper-aware-placement--max-errors--minimize-on-fail--mirroring--nes-chr--nes-chr-ram--nes-local-ram--nes-opt--nes-opt-report--nes-temp-ram--no-auto-inline-small--no-battery--no-cache--no-disasm--no-fastcall-v2--no-library-lto-lite--no-loop-lowering--no-lto-lite--no-mapper-aware-placement--no-reg-cc-v2--no-rst--no-small-inline--no-static-frame--no-whole-program-zp--no-zp-alloc--opt-diff--output-format--permissive--profile--ramsize--reg-cc-v2--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--small-inline--static-frame--strict--target--trace--trace-out--verify-cgb-symbols--version--vlist--vlist-out--whole-program-zp--zp-allocAnhang: Verzeichnis der Assemblerbefehle
Diese Tabelle wurde aus src/machine/opcodes.rs erstellt. Sie beschreibt die internen Befehlsnamen und Operandenformate des Compilers. Einträge mit Sprungzielen oder Speicheradressen sind Syntaxbeispiele und keine eigenständigen Programme. Informationen zu erhaltenen Registern und veränderten Flags finden Sie in den Aufrufkonventionen und der Codegenerierung.
| opcode | Befehl | Format | Syntaxbeispiel |
|---|---|---|---|
| 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 |
| 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 |
| 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 |