KITAQ SYSTÈME DE DÉVELOPPEMENT
MANUEL D'UTILISATION 2026.09

KITAQ SERIES • ÉDITION DE RÉFÉRENCE

KITAQFC

Programmation C pour Famicom

De votre première ligne à l'exécution, à l'observation et aux nouveaux tests.
Copie des sources : 14 septembre 2026 · Édition anglaise de référence : 14 septembre 2026

Compilateur natif Rust et outils auxiliaires

Rust 1.85 ou ultérieur permet de construire le compilateur et tous les outils auxiliaires pour Windows, Linux, macOS ARM et macOS Intel. Les exécutables natifs ne nécessitent pas .NET ; les outils de ressources fonctionnent aussi sans Python ni Pillow.

Les exécutables Windows se trouvent à la racine du dépôt. Ceux de Linux et macOS sont dans les dossiers bin/ indiqués ci-dessous. Conservez lib/ et les mentions de licence avec les outils. Sous Linux/macOS, attribuez les droits d'exécution avec chmod +x, puis ajoutez le dossier au PATH ou utilisez le chemin complet.

OSkitaqfc
Windows x64kitaqfc.exe
Linux x64bin/Linux-X64/kitaqfc
macOS ARM64bin/macOS-ARM64/kitaqfc
macOS Intelbin/macOS-X64/kitaqfc

kitaqfc · Compilateur natif Rust et outils auxiliaires · GitHub Actions

Les constructions natives et vérifications d'exécution ont réussi sous Windows, Linux, macOS ARM et macOS Intel. KITAQGB a passé 48 tests et 395 vérifications d'outils par environnement ; KITAQFC en a passé 55 et 401. Rust 1.85 a également été testé. PUBLIC_DISTRIBUTION.json consigne les empreintes des binaires installés et la provenance des validations. Les workflows GitHub Actions publics reconstruisent et testent ces sources indépendamment.

Les sorties de référence conservées vérifient les octets ROM, les diagnostics et les formats des outils. Les anciennes preuves d'émulation C# restent des résultats historiques liés à leurs empreintes sources. Elles ne prouvent pas automatiquement toutes les API Rust, le matériel réel ou le démarrage complet d'un jeu par le BIOS FDS. Le script PNG d'origine, indisponible, a été réimplémenté d'après sa spécification ; l'identité des octets avec ce script ne peut être affirmée.

--attach est accepté pour compatibilité, mais ne connecte pas de débogueur managé à l’exécutable natif. Utilisez un débogueur natif pour le programme hôte.

KITAQGB · SHA-256 · KITAQFC · SHA-256

Construction portable des exemples

Les scripts PowerShell utilisent l'exécutable Windows à la racine. Sous Linux/macOS, transmettez les mêmes sources C et options au compilateur natif, ou utilisez PowerShell 7. Les graphismes CHR et le source C sont des entrées distinctes ; font.chr conserve la police des exemples.

sh
kitaqfc game.c -I lib -o game.nes --mapper=nrom

Distribution actuelle du compilateur et des bibliothèques — 4 octobre 2026

Utilisez kitaqfc.exe et lib provenant de la même copie du dépôt. Depuis le répertoire parent, lancez le script Release ci-dessous ; le projet copie l'exécutable à la racine du dépôt. BINARY_BUILD.json consigne les empreintes des sources et de l'exécutable ainsi que les vérifications de cette compilation. Les longues lignes de diagnostic sont limitées à 200 caractères suivis de points de suspension.

La compilation C# actuelle a été comparée à la distribution précédente sur 29 entrées. Quinze entrées acceptées ont produit les mêmes octets ROM ; les quatorze autres entrées ont produit les mêmes diagnostics de rejet. Deux ROM supplémentaires ont tourné pendant quatre images dans KUROSAKI : allocation d'entités, rappels et réutilisation des emplacements ; calcul signé, disposition de KQBody3D et constantes de la file audio. Le rapport donne 18 octets RAM attendus et observés. Ces essais ne couvrent ni la lecture audio ni toutes les API ; les anciennes régressions sont distinguées dans BINARY_BUILD.json.

.\kitaqfc\scripts\build.ps1
.\kitaqfc\kitaqfc.exe --help

Exemples source et preuves d'exécution

1. KITAQFC et le compilateur GB

KITAQFC reprend la partie frontale de KITAQGB pour générer du code destiné au processeur de la famille 6502 de la NES/Famicom. Il ne convertit pas une ROM GB en ROM NES. Écrivez le logiciel pour l'affichage, le son, la mémoire et le mapper de la machine cible.

KITAQFC prend en charge les copies de structures, les appels de fonctions ordinaires et les boucles for, while et do-while. Une boucle do-while exécute son corps au moins une fois avant de tester la condition. continue passe à ce test final ; break quitte la boucle. Un switch sélectionne une constante case comprise entre 0 et 255, ou le corps default si aucun case ne correspond. Son expression de sélection n’est évaluée qu’une fois. break quitte la boucle ou le switch le plus interne ; un continue à l’intérieur d’un switch passe à l’itération suivante de la boucle englobante.

2. Prérequis et compilation

Rust 1.85 ou ultérieur permet de construire le compilateur et tous les outils auxiliaires pour Windows, Linux, macOS ARM et macOS Intel. Les exécutables natifs ne nécessitent pas .NET ; les outils de ressources fonctionnent aussi sans Python ni Pillow.

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

Les exécutables Windows se trouvent à la racine du dépôt. Ceux de Linux et macOS sont dans les dossiers bin/ indiqués ci-dessous. Conservez lib/ et les mentions de licence avec les outils. Sous Linux/macOS, attribuez les droits d'exécution avec chmod +x, puis ajoutez le dossier au PATH ou utilisez le chemin complet.

OSkitaqfc
Windows x64kitaqfc.exe
Linux x64bin/Linux-X64/kitaqfc
macOS ARM64bin/macOS-ARM64/kitaqfc
macOS Intelbin/macOS-X64/kitaqfc
powershell
.\scripts\build.ps1
.\kitaqfc.exe --help
sh
sh scripts/build.sh

Les scripts PowerShell utilisent l'exécutable Windows à la racine. Sous Linux/macOS, transmettez les mêmes sources C et options au compilateur natif, ou utilisez PowerShell 7. Les graphismes CHR et le source C sont des entrées distinctes ; font.chr conserve la police des exemples.

3. Votre premier programme

c
#include "fc_common.h"
void main(void) {
    m_init();
    m_text(2, 5, "HELLO WORLD");
    m_number(42);
    while (1) { m_wait(); }
}
powershell
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\fc_hello.c -I .\kitaq-docs\samples -I .\kitaqfc\lib --mapper=nrom --nes-chr=.\kitaq-docs\samples\font.chr -o .\out\hello.nes --kurosaki-metadata=.\out\hello.debug.json

L'exemple affiche HELLO WORLD et 042. Sa fonction m_wait valide la file VRAM, attend la NMI et restaure le défilement. Les transferts passant par PPUADDR modifient l'état interne du défilement ; sans restauration, le texte peut être décalé vers un bord. Retenez cette séquence : désactiver l'affichage, préparer les ressources, réactiver l'affichage, puis se synchroniser avec la NMI.

4. Introduction au langage

Les instructions et expressions du volume GB constituent une base commune. FC accepte unsigned char et unsigned short ; core.h définit u8, u16, s8 et s16. fc.h est l'en-tête de regroupement. Ici, les fonctions sans argument peuvent s'écrire sous la forme void main(void).

c
#include "core.h"
u8 clamp_score(u8 n) {
    if (n > 99) return 99;
    return n;
}

Respectez les plages des entiers sur 8 ou 16 bits. Les indices des tableaux commencent à zéro. fc_aggregate.c présente les fonctions, pointeurs et structures ; fc_arithmetic.c, les calculs ; fc_control.c, les boucles. N'incluez pas de registres CGB ni de fonctions intrinsèques réservées à la GB dans un programme FC.

Résultats des expressions et évaluation

Les opérateurs de comparaison ==, !=, <, <=, >, >= et les opérateurs logiques !, &&, || renvoient 0 pour faux et 1 pour vrai. Vous pouvez stocker le résultat dans un u16, le passer en argument, le renvoyer ou l’utiliser dans un calcul. Par exemple, score = 500 + (lives != 0); donne 501 s’il reste une vie, et 500 sinon.

&& n’évalue pas son opérande droit lorsque celui de gauche vaut zéro. || n’évalue pas son opérande droit lorsque celui de gauche est non nul. Dans pointer != 0 && pointer->active != 0, un pointeur nul empêche l’accès au membre. Les deux octets d’une valeur de 16 bits participent au test logique : 256 est donc vrai. Les opérateurs bit à bit & et | ne court-circuitent pas l’évaluation.

++value renvoie la valeur mise à jour ; value++ renvoie la valeur initiale. Ces opérateurs acceptent aussi les éléments de tableau, les pointeurs déréférencés et les membres de structure. buffer[index()]++ appelle index() une seule fois. Avec u16 *p, p++ avance de deux octets vers l’élément suivant, tandis que (*p)++ incrémente la valeur pointée.

sizeof(array) donne la taille en octets de tout le tableau ; sizeof(pointer) vaut 2. Pour u16 values[9];, sizeof(values) vaut 18. Cela s’applique aux tableaux en ROM, aux tableaux locaux et aux membres de type tableau. sizeof(function()) examine le type de retour sans appeler la fonction.

La déclaration et la définition d’une fonction doivent avoir les mêmes types de paramètres, dans le même ordre. Les noms peuvent différer ; le corps utilise ceux de la définition. Par exemple, u8 next(u8 input); peut être défini par u8 next(u8 value) { return (u8)(value + 1); }. Les paramètres et variables locales masquent les variables globales de même nom.

La sélection de tableaux ou de chaînes avec ?: produit un pointeur vers le type d’élément sélectionné. Vous pouvez le passer directement, par exemple dans show(ready ? "READY" : "WAIT");. Pour des tableaux u16 nommés a et b, (ready ? a : b) + 1 avance de deux octets vers le deuxième élément du tableau choisi. Le tableau n’est pas copié.

condition ? yes : no évalue la condition, puis uniquement la branche choisie. La condition et les branches peuvent contenir des décalages dont le nombre de bits est variable. Par exemple, on = (pattern & (0x80 >> bit)) != 0 ? 4 : 2; choisit quatre ou deux selon le bit désigné. Les appels de fonction présents dans un opérande droit ignoré par && ou || sont eux aussi ignorés. Un continue dans une boucle for exécute une fois l’expression de mise à jour avant de réévaluer la condition ; dans while et do ... while, il passe à la condition.

5. Boucles et répartition par état

c
do {
    update();
} while (condition);

// Dispatch a state with if/else.
if (state == 0) { title_update(); }
else if (state == 1) { game_update(); }
else { pause_update(); }

Ces fragments montrent une boucle qui effectue au moins une mise à jour et une branche qui choisit le traitement de l’état courant. Définissez update, condition, state et les traitements des états dans votre programme. Consultez fc_control.c pour un exemple complet de boucle, et l’exemple de gestion des images et des scènes de la bibliothèque pour une ROM complète de gestion des scènes.

Enregistrez les rappels de scène, d’entité et de système avec les types d’arguments et de retour exigés par leurs déclarations. Les bibliothèques appellent les traitements enregistrés depuis les opérations correspondantes de mise à jour, de dessin ou d’attente d’image. Respectez les contraintes de banque ROM et de mappage de chaque API. Ne supposez pas que la récursion ou les fonctions variadiques sont prises en charge comme sur un ordinateur de bureau.

6. Mémoire et PPU

La RAM interne du processeur NES occupe 0x0000–0x07FF. Ses miroirs au-delà de 0x0800 ne constituent pas de la RAM supplémentaire. La pile du 6502 occupe la page 1 ; les copies de travail de l'OAM et les files réservent d'autres régions. Les réglages avancés --nes-local-ram=START:LENGTH et --nes-temp-ram=START:LENGTH nécessitent d'examiner le fichier de placement.

Les adresses PPU appartiennent à un espace distinct. Le CHR fournit les motifs, les tables de noms placent les tuiles, les tables d'attributs choisissent les groupes de palettes et les palettes contiennent les codes de couleur. Les attributs d'arrière-plan s'appliquent normalement à des régions de 16 × 16 pixels ; ils ne fonctionnent donc pas comme les attributs de tuiles GB.

7. NMI et file VRAM

La NMI est l'interruption associée à la limite entre deux images affichées. Des écritures directes importantes dans le PPU pendant le rendu peuvent corrompre l'écran. Effectuez l'initialisation directe lorsque le rendu est désactivé ; pour les mises à jour ordinaires, utilisez __vramq_put, __vramq_copy, __vramq_fill, puis validez la file.

c
__vramq_put(0x2000 + 8 * 32 + 3, '4');
__vramq_commit();
__nmi_wait();
__scroll_set(0, 0);

Vérifiez la capacité de la file et la durée de validité des sources. La NMI par défaut traite la file. Un gestionnaire __nes_nmi personnalisé doit conserver l'exécution nécessaire de la file, le travail sur l'OAM et la préservation des registres.

8. Mappers et disposition de la ROM

ChoixPremier usage typique
nromPetits exercices avec ROM fixe
uxrom / cnrom / axromCommutation simple de PRG ou de CHR
mmc1 / mmc3 / mmc5Programmes plus grands et fonctions propres au mapper
vrc6 / vrc7 / fme7Banques et extensions correspondantes
fdsProduction d'images de disque

Il s'agit des choix du compilateur, pas d'un tableau de compatibilité matérielle ou d'exhaustivité de l'émulateur. --board=surom512 sélectionne une organisation particulière de carte MMC1 ; compléter simplement un fichier jusqu'à 512 Kio ne crée pas cette organisation. Utilisez également l'audit de carte de KUROSAKI.

powershell
.\kitaqfc\kitaqfc.exe main.c --mapper=mmc3 --nes-chr=tiles.chr --mirroring=vertical -o .\out\game.nes --kurosaki-metadata=.\out\game.debug.json --emit-ai-metadata=.\out\build.json

Vérifiez les exigences de la carte pour --battery / --no-battery, la capacité CHR, le placement PRG et les appels entre banques. Après un changement de mapper ou de mode miroir, testez le démarrage, le défilement et la commutation des données, en plus de la génération de la ROM.

8.1 RAM CHR pour des graphismes modifiables

--nes-chr-ram produit une ROM de cartouche utilisant 8 KiB de RAM CHR accessible en écriture. Le fichier ROM ne contient aucune donnée CHR : le programme doit donc transférer les motifs des tuiles vers le PPU à l’initialisation. Le moteur de rendu wire3d.c utilise ce mode.

powershell
New-Item -ItemType Directory -Force .\out | Out-Null
.\kitaqfc\kitaqfc.exe .\kitaq-docs\samples\api-examples\fc\wire3d_projection.c -I .\kitaqfc\lib --mapper=nrom --nes-chr-ram -o .\out\wire3d_projection.nes

--nes-chr=tiles.chr ou --chr-rom=tiles.chr intègre des motifs préparés dans une ROM CHR. Aucun de ces deux paramètres ne peut être combiné avec --nes-chr-ram. Le profil CNROM refuse également le mode RAM CHR. Vérifiez que le mapper et la carte ciblés disposent de la RAM CHR nécessaire.

Sans option CHR, le compilateur intègre une ROM CHR vide de 8 KiB. Sélectionnez explicitement --nes-chr-ram pour les exemples qui transfèrent des motifs pendant l’exécution. La RAM CHR est distincte de la RAM PRG côté CPU ; le tampon de préparation du rendu filaire nécessite aussi sa propre allocation de RAM PRG.

Les lectures ordinaires par indice et les références par pointeur C à des tableaux en ROM placent ces tableaux dans la banque commune 0, sauf si #pragma fixed_bank fixe explicitement leur emplacement. Les données restent ainsi visibles lorsque l’appelant s’exécute dans une banque commutable. Pour un accès distant explicite, passez dans le même appel le nom du tableau seul et __bankof appliqué à ce même nom, par exemple __farpeek8(__bankof(table), table). Une simple demande de numéro de banque ne demande pas de placement en banque commune. Pour les données explicitement fixées à une banque et les références en assembleur, vous devez maintenir le mappage correct. Cette règle vérifie le placement à partir de la syntaxe ; elle n’analyse pas le cheminement des pointeurs. La capacité de la banque commune reste limitée.

9. FDS, extensions sonores et périphériques

Le FDS implique le placement des fichiers sur disque, le démarrage, les overlays et les sauvegardes. Consultez fds_manifest_sample.json et les en-têtes FDS. Préparez le BIOS éventuellement nécessaire dans votre propre environnement d'exécution ; la distribution publique ne contient aucun BIOS.

Appeler une opération sonore VRC6 ou VRC7 ne change pas le mapper choisi pour la ROM. Faites correspondre le mapper à l'extension sonore employée. Pour les périphériques, testez séparément la lecture des entrées, l'état de connexion et les effets sur les manettes ordinaires.

10. Diagnostics et résultats de compilation

Les diagnostics KQ et les commandes de développement comme symfind, src2asm et romdiff ressemblent à leurs équivalents GB. Certaines options d'aide héritées de la GB ne correspondent pas nécessairement à des fonctions NES implémentées. Le dictionnaire FC est extrait séparément des sources et en-têtes FC.

Des avertissements comme KQ2421, relatif aux accès directs au PPU, peuvent apparaître même pendant une initialisation avec affichage désactivé. Ne modifiez pas une initialisation sûre uniquement pour supprimer un avertissement : examinez le moment du rendu et les journaux d'exécution. Zéro erreur et zéro avertissement sont deux résultats différents.

11. Exemple pratique : développer un jeu avec l’IA

Renseignez les besoins, puis transmettez le prompt complet à votre assistant IA. Il couvre l’implémentation, les tests dans l’émulateur, l’analyse avec SARAKURA et la vérification des corrections.

· Télécharger le Markdown

Renseignez les besoins, puis transmettez ce document entier à l’assistant IA. Les commandes supposent que les dépôts kitaqgb, kitaqfc, kokura, kurosaki, sarakura et kitaq-docs, ainsi que le projet game-gb ou game-fc, se trouvent dans le même dossier parent. Exécutez-les depuis ce dossier et adaptez les chemins à votre environnement.

Besoins

  • Titre du jeu : <à renseigner>
  • Genre et mécanique principale : <à renseigner>
  • Commandes et conditions de réussite ou d’échec : <à renseigner>
  • Écrans, niveaux, ennemis et objets indispensables : <à renseigner>
  • Style graphique, musique et effets sonores : <à renseigner, avec les chemins des ressources fournies>
  • Sauvegarde, communication, périphériques et autres besoins : <à renseigner, ou aucun>
  • Dossier du projet : <à renseigner>
  • Conditions de redistribution : <par exemple, code et ressources originaux pouvant être publiés sous licence MIT>
  • Cible : <cartouche NES/Famicom ou FDS>
  • Mapper, taille de ROM et mirroring : <à préciser ou à choisir selon les besoins>
  • Standard vidéo et performances : <par exemple, NTSC et 60 mises à jour de la logique par seconde>

Travail demandé

Implémentez le jeu avec KITAQFC et ses bibliothèques. Utilisez KUROSAKI pour l’exécution et le débogage, et SARAKURA pour organiser les diagnostics et comparer les résultats avant et après correction.

Répétez ce cycle jusqu’à satisfaire les critères d’acceptation : préciser la spécification → implémenter une petite modification → compiler → appliquer des entrées et observer → rechercher la cause → corriger → refaire les tests dans les mêmes conditions. Un plan, du code fourni ou une compilation réussie ne suffisent pas à terminer le travail.

Vérifier l’environnement et les critères d’acceptation

  1. Lisez les consignes du dossier de travail, les README, les manuels HTML ainsi que les en-têtes et implémentations des bibliothèques utilisées. Relevez les chemins des exécutables et leurs versions ou empreintes SHA-256. Vérifiez les commandes dans la sortie réelle de --help et les API dans le code source.
  2. Définissez des critères mesurables pour les entrées, l’image, le son, la progression et la fréquence de mise à jour. Par exemple : appuyer puis relâcher START lance la partie ; une collision retire une vie ; la pause coupe les sons prévus et la reprise rétablit la lecture.
  3. Ne posez de questions que sur les ambiguïtés importantes. Prenez de façon autonome les décisions courantes et réversibles. Ne réduisez pas les exigences ni les critères d’acceptation.
  4. Commencez par faire passer un petit exemple fourni dans le compilateur, l’émulateur et SARAKURA. Cela vérifie leur articulation, pas l’achèvement du jeu demandé.

Réaliser une première version jouable

  • Envisagez NROM pour un petit jeu, puis MMC3 ou un autre mapper si la taille ou les changements de banque le nécessitent. Vérifiez les fonctions de la carte avec inspect-rom, mapper-info, audit-board et leur implémentation ; le nom du mapper ne garantit pas leur prise en charge.
  • Prévoyez les tailles PRG/CHR, CHR-ROM ou CHR-RAM, mirroring, banques fixes, vecteurs d’interruption et RAM de sauvegarde. Après optimisation ou modification des banques, comparez l’en-tête à la disposition réelle. --nes-local-ram utilise la RAM interne du CPU $0000–$07FF ; évitez tout chevauchement avec page zéro, pile, tampons OAM et zones du runtime ou des bibliothèques.
  • Utilisez le dialecte C de KITAQFC, les bibliothèques FC et void main(void). Ne présumez pas la compatibilité des API GB. Certains éléments des en-têtes ne sont que des déclarations : repérez les implémentations et incluez les fichiers .c nécessaires.
  • Tenez compte des registres PPU, de NMI, d’OAM DMA, du nombre de sprites par ligne, du défilement, du mirroring, des tables d’attributs et d’APU/DMC. L’espace libre de la file n’est pas la capacité de VRAM du PPU ; dimensionnez le travail de chaque NMI.
  • Convertissez la police originale ascii.c au format CHR FC, puis vérifiez CHR, palettes, tables de noms et attributs. Pour FDS, vérifiez séparément accès disque, sauvegarde et exigences de BIOS, sans supposer les mêmes conditions de démarrage qu’une cartouche.
  • Reliez d’abord démarrage, titre, personnage contrôlable, réussite ou échec, et nouvelle partie. Enrichissez ensuite le contenu.
  • Conservez les sources modifiables des graphismes, musiques et effets ainsi que les étapes de génération. Vérifiez que la compilation utilise réellement les données exportées.
  • Rédigez les commentaires du code en anglais et les comptes rendus d’avancement en français. Gardez les rapports standard de SARAKURA en anglais.

Relier chaque compilation à son exécution

Séparez les sorties par itération, par exemple dans out/iter-001. Consignez commandes, codes de sortie et empreintes du code, des ressources, outils, ROM et métadonnées. N’exécutez jamais une ancienne ROM après une compilation échouée. Les cartes mémoire, correspondances avec les sources et informations de débogage doivent provenir de la même compilation que la ROM.

Cet exemple vérifie NROM sans entrée. Préparez main.c, les implémentations nécessaires et le fichier CHR ; choisissez le mapper approprié. Ne transmettez pas les métadonnées de compilation à --kitaqfc-debug de KUROSAKI sans vérifier le format attendu.

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

# Include all additional implementation units required by the game.
& '.\kitaqfc\kitaqfc.exe' '.\game-fc\src\main.c' `
  -I '.\kitaqfc\lib' -o "$iteration\game.nes" `
  --mapper=nrom '--nes-chr=.\game-fc\assets\game.chr' --no-disasm `
  "--kurosaki-metadata=$iteration\build.json"
if ($LASTEXITCODE -ne 0) { throw 'Build failed; inspect the build log.' }

& '.\kurosaki\kurosaki.exe' run "$iteration\game.nes" `
  --frames 300 --pad1 0 --png "$iteration\frame.png" `
  --json "$iteration\run.json" --emit-diagnostics "$iteration\events.jsonl"
if ($LASTEXITCODE -ne 0) { throw 'Emulator run failed; inspect the run log.' }

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

Une exécution de 300 images sans entrée n’est qu’une vérification initiale. Ajoutez des scénarios avec des actions successives avant d’affirmer que le jeu fonctionne.

Reproduire une séquence d’entrées

  • --pad1 et --pad2 utilisent des masques de bits NES bruts : A=1, B=2, SELECT=4, START=8, UP=16, DOWN=32, LEFT=64, RIGHT=128. Ne les confondez pas avec les valeurs BTN_* de la bibliothèque.
  • run --pad1 applique une entrée fixe. Pour enchaîner des actions, préparez un replay distinguant pression, maintien et relâchement. Consultez les définitions Replay et ReplayFrame. Dans la CLI publique, replay-record enregistre des entrées neutres, pas une partie jouée par une personne.
  • Contrôlez vous-même le SHA-256 de la ROM, la cible du replay et la plage d’images. replay-run --verify ne compare l’empreinte finale attendue que si elle existe ; il ne valide pas entièrement le jeu ni l’identité de la ROM. Ne remplacez pas systématiquement les résultats attendus par les résultats observés pour faire réussir un test.
powershell
& '.\kurosaki\kurosaki.exe' replay-run `
  '.\game-fc\out\iter-001\game.nes' '.\game-fc\tests\start-and-play.replay.json' `
  --frames 900 --json '.\game-fc\out\iter-001\play.json' `
  --png '.\game-fc\out\iter-001\play.png' `
  --wav '.\game-fc\out\iter-001\play.wav'

Préparez le replay pour la ROM testée. La CLI publique ne propose pas --emit-diagnostics avec replay-run. N’inventez pas cette option et ne transmettez pas de trace CPU à la place d’événements de diagnostic. Pour analyser des entrées successives avec SARAKURA, créez dans le projet un programme de test utilisant les API publiques de kurosaki-core : RunOptions.replay_frames et diagnostic_events_from_trace_and_report. Exécutez la même ROM, le même replay et les mêmes conditions d’images ; produisez le JSONL à partir de la trace et du rapport diagnostic de cette exécution. Vérifiez la configuration et la plage conservée de la trace, puis comparez images et observations du programme de test avec le replay de la CLI. Ne présentez pas des diagnostics sans entrée comme preuve d’un scénario de jeu. Si l’environnement nécessaire manque, signalez cette vérification comme incomplète.

Vérifier l’image, le son, l’état et les performances

  • Conservez les scénarios d’entrée en distinguant pression, maintien et relâchement. Parcourez toutes les voies prévues : démarrage, début de partie, déplacement, actions, collisions, défilement, changement de niveau, fin de partie, redémarrage, pause et, si nécessaire, sauvegarde ou communication.
  • Gardez les PNG des images pertinentes, les entrées, rapports d’exécution, diagnostics JSONL, WAV et observations nécessaires d’état ou de mémoire. Vérifiez le nombre d’images atteint et la raison de l’arrêt. Ouvrez réellement les images : une seule capture ne prouve ni mouvement ni réponse aux commandes. Comparez compteurs, positions et transitions aux valeurs attendues ; examinez bords d’écran, limites de tuiles et d’attributs, et scènes chargées en sprites.
  • Vérifiez musique, effets, lecture simultanée, coupures, pause et reprise. Produire un WAV ne prouve pas la justesse du son. Si l’écoute est impossible, distinguez les contrôles numériques ou de forme d’onde réalisés des qualités sonores non vérifiées.
  • Mesurez les scènes exigeantes, le travail du CPU cible, les mises à jour et les transferts ; sur FC, incluez NMI. Le débit de l’émulateur sur l’ordinateur hôte n’est ni la fréquence de mise à jour du jeu ni une preuve de vitesse sur matériel réel. Poursuivre avec --allow-unimplemented ne prouve pas la prise en charge de la fonction manquante.

Analyser, corriger et refaire les tests

  • Fournissez à SARAKURA les métadonnées de la ROM testée et le JSONL diagnostic de cette exécution. Une trace CPU ou un rapport d’exécution ordinaire ne les remplace pas. --frames précise les conditions d’analyse ; SARAKURA n’exécute pas la ROM et ne modifie pas automatiquement les sources.
  • Lisez report.html, ai_diagnostics.json, repair_prompt.md et retest_plan.json. Confrontez les diagnostics aux étapes de reproduction, images, sons et sources. Distinguez les emplacements ou causes supposés des faits vérifiés, et les boucles d’attente normales des blocages. Examinez chaque avertissement et notez les événements non pris en charge ou les limites d’analyse. Ne masquez pas les avertissements avec des filtres et ne raccourcissez pas les tests pour obtenir un succès.
  • Réduisez les défauts à des cas minimaux, corrigez leur cause et recompilez. Si le compilateur ou l’émulateur est en cause, isolez son défaut du code du jeu et ajoutez une vérification de non-régression à la correction de l’outil.
  • Refaites les tests avec les mêmes entrées, graine aléatoire, machine et standard vidéo, mapper, images observées et réglages diagnostics. Utilisez les métadonnées propres à chaque ROM ; ne réutilisez pas aveuglément les états sauvegardés après modification du code ou de la disposition RAM.
powershell
& '.\sarakura\sarakura.exe' baseline-delta `
  --baseline '.\game-fc\out\iter-001\analysis' `
  --current '.\game-fc\out\iter-002\analysis' `
  --out '.\game-fc\out\delta.json' --markdown '.\game-fc\out\delta.md' `
  --fail-on-new error --fail-on-regression error --enforce

Associez les différences de diagnostic aux critères d’acceptation des commandes, graphismes et sons. Si le même échec se répète, réexaminez les preuves et l’hypothèse au lieu d’enchaîner des modifications arbitraires.

Conditions de fin et livrables

Rejouez tous les scénarios obligatoires avec la ROM finale compilée depuis les sources et réglages livrés. L’invincibilité, des entrées automatiques de test ou un autre mapper ne suffisent pas à valider une partie normale dans la version finale. Fournissez un tableau reliant besoins et tests, expliquez les avertissements restants et indiquez les éléments non vérifiés ou non pris en charge. Précisez explicitement l’absence de tests sur matériel physique, le cas échéant.

Livrez les sources, l’identification des outils et bibliothèques, les ressources modifiables, les scripts reproductibles de compilation et de test, la ROM, les preuves finales et un README expliquant installation, commandes et limites connues. Incluez les replays et le programme de test si nécessaire. Publiez ou transmettez des fichiers à l’extérieur uniquement dans le périmètre expressément autorisé. Supprimez les compilations intermédiaires et traces temporaires inutiles après vérification, mais conservez sources, ressources, livrables et preuves de non-régression nécessaires.

Si l’environnement ou les permissions empêchent un contrôle obligatoire, indiquez les étapes exactes de reproduction et l’action nécessaire. Ne déclarez pas le travail terminé.

Emplacement des sources

Les sources du compilateur et le fichier projet se trouvent dans le sous-dossier portant le nom du dépôt. L’exécutable est à la racine et les bibliothèques dans lib/. Consultez la structure des dossiers pour les chemins et les prérequis de compilation.

Exemples de programmes complets

Chaque programme, les en-têtes communs, le fichier ascii.c fourni et ses ressources converties sont inclus. Les noms commençant par m_ sont des fonctions écrites pour ces manuels. Lisez chaque commande de compilation avec son résultat attendu et son compte rendu de vérification. Lancez les commandes depuis le dossier parent des dépôts voisins.

Script de compilation par lot / Résultats et captures d'écran

Afficher le jeu de caractères fourni fc_font.c

Objectif : Présente les lettres, chiffres et signes de ponctuation de la police originale fournie, ainsi que leur affichage.

Fonctionnement : m_init prépare l’affichage et la police. m_text dessine chaque ligne ; m_put dessine le guillemet double avec le code de caractère 34.

Commandes et résultat attendu : Repérez les 26 lettres majuscules, les 26 minuscules, les 10 chiffres et les 30 signes de ponctuation. Vérifiez que chaque glyphe est lisible. Aucune saisie n’est nécessaire.

Afficher le jeu de caractères fourni : Repérez les 26 lettres majuscules, les 26 minuscules, les 10 chiffres et les 30 signes de ponctuation. Vérifiez que chaque glyphe est lisible. Aucune saisie n’est nécessaire.
Exemple d’affichage : Repérez les 26 lettres majuscules, les 26 minuscules, les 10 chiffres et les 30 signes de ponctuation. Vérifiez que chaque glyphe est lisible. Aucune saisie n’est nécessaire.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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();  }
}
Votre premier affichage de texte fc_hello.c

Objectif : Présente les étapes de base pour démarrer une ROM et dessiner du texte et un nombre sur le fond.

Fonctionnement : Après m_init, m_text dessine le message d’accueil et m_number formate 42. La boucle finale attend les images en conservant l’affichage.

Commandes et résultat attendu : L’écran affiche HELLO WORLD et 042. Le zéro initial provient du format décimal sur trois chiffres.

Votre premier affichage de texte : L’écran affiche HELLO WORLD et 042. Le zéro initial provient du format décimal sur trois chiffres.
Exemple d’affichage : L’écran affiche HELLO WORLD et 042. Le zéro initial provient du format décimal sur trois chiffres.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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();  }
}
Calculer avec un intermédiaire plus large fc_arithmetic.c

Objectif : Montre comment conserver un résultat intermédiaire qui dépasse huit bits.

Fonctionnement : (u16)200 + 100 donne 300. Le programme divise ce résultat par 10, puis le convertit en u8 pour l’afficher. Notez que la conversion vers le type plus large précède l’addition.

Commandes et résultat attendu : 030 représente le résultat calculé, 30.

Calculer avec un intermédiaire plus large : 030 représente le résultat calculé, 30.
Exemple d’affichage : 030 représente le résultat calculé, 30.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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();  }
}
Branchements et boucles fc_control.c

Objectif : Essaie for, continue, while et if/else dans un petit calcul.

Fonctionnement : La boucle for additionne les valeurs de 0 à 7 en sautant 3, ce qui donne 25. La boucle while avance jusqu’à 32 ; le if suivant affecte 42 lorsque cette valeur est atteinte.

Commandes et résultat attendu : 042 est le résultat final. Ce programme utilise un branchement if/else.

Branches et boucles : le résultat final est 042. Ce programme utilise une branche if/else.
Exemple d’affichage : 042 est le résultat final. Ce programme utilise un branchement if/else.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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();  }
}
Fonctions, tableaux, structures et pointeurs fc_aggregate.c

Objectif : Montre comment regrouper des valeurs et y accéder par des fonctions et des pointeurs.

Fonctionnement : Le programme copie un Point contenant (10,20), puis écrit de 1 à 4 dans un tableau via un pointeur. Il additionne le résultat de fonction 30, les valeurs du tableau 1, 2 et 4 et la constante 5.

Commandes et résultat attendu : La somme est affichée sous la forme 042. Suivez la copie de structure, les écritures dans ptr[i] et la valeur renvoyée par la fonction.

Fonctions, tableaux, structures et pointeurs : La somme est affichée sous la forme 042. Suivez la copie de structure, les écritures dans ptr[i] et la valeur renvoyée par la fonction.
Exemple d’affichage : La somme est affichée sous la forme 042. Suivez la copie de structure, les écritures dans ptr[i] et la valeur renvoyée par la fonction.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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();  }
}
Drapeaux binaires et copie de tampon fc_bits_memory.c

Objectif : Regroupe des drapeaux dans un octet et copie un tampon dans une zone distincte.

Fonctionnement : __memset efface quatre octets. Le programme active le bit 3, inverse le bit 1, puis copie les quatre octets avec __memcpy.

Commandes et résultat attendu : le premier octet de destination est affiché sous la forme 010 ; le bit 3 apporte 8 et le bit 1 apporte 2.

Drapeaux binaires et copie de tampon : le premier octet de destination est affiché sous la forme 010 ; le bit 3 apporte 8 et le bit 1 apporte 2.
Exemple d’affichage : le premier octet de destination est affiché sous la forme 010 ; le bit 3 apporte 8 et le bit 1 apporte 2.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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();  }
}
Compter les nouveaux appuis sur un bouton fc_input.c

Objectif : Distingue un nouvel appui sur un bouton d’un bouton maintenu enfoncé.

Fonctionnement : Chaque image appelle input_update. Le compteur n’avance que si input_pressed(BTN_A) est vrai.

Commandes et résultat attendu : le compteur commence à 000. Appuyez sur A pour obtenir 001 ; maintenir le bouton n’ajoute rien. Relâchez-le puis appuyez de nouveau pour obtenir 002. Le compteur sur un octet revient de 255 à 000.

Compter les nouveaux appuis sur un bouton : le compteur commence à 000. Appuyez sur A pour obtenir 001 ; maintenir le bouton n’ajoute rien. Relâchez-le puis appuyez de nouveau pour obtenir 002. Le compteur sur un octet revient de 255 à 000.
Exemple d’affichage : le compteur commence à 000. Appuyez sur A pour obtenir 001 ; maintenir le bouton n’ajoute rien. Relâchez-le puis appuyez de nouveau pour obtenir 002. Le compteur sur un octet revient de 255 à 000.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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); } }
}
Multiplier des valeurs à virgule fixe Q8.8 fc_fixed.c

Objectif : Présente la conversion d’entier en Q8.8, la multiplication et le retour à un entier.

Fonctionnement : fix_from_int convertit 3 et 4 en Q8.8. fix_mul les multiplie, puis fix_to_int reconvertit le produit en entier.

Commandes et résultat attendu : 012 signifie 3 × 4 = 12. C’est l’entier converti, pas la valeur interne multipliée par 256.

Multiplier des valeurs à virgule fixe Q8.8 : 012 signifie 3 × 4 = 12. C’est l’entier converti, pas la valeur interne multipliée par 256.
Exemple d’affichage : 012 signifie 3 × 4 = 12. C’est l’entier converti, pas la valeur interne multipliée par 256.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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();  }
}
Allouer dans un pool fixe d’objets fc_entity.c

Objectif : Crée un objet dans un ensemble fixe d’emplacements, sans allocation dynamique.

Fonctionnement : entity_init réinitialise le pool. entity_create crée un objet de type 1 en (20,30) ; le programme vérifie l’ID d’échec 0xFF avant de fixer vx à 2.

Commandes et résultat attendu : 001 est le nombre d’objets actifs, pas un ID ni une coordonnée. Cet exemple ne met pas à jour et ne dessine pas l’objet ; aucun personnage ne se déplace donc à l’écran.

Allouer dans un pool fixe d’objets : 001 est le nombre d’objets actifs, pas un ID ni une coordonnée. Cet exemple ne met pas à jour et ne dessine pas l’objet ; aucun personnage ne se déplace donc à l’écran.
Exemple d’affichage : 001 est le nombre d’objets actifs, pas un ID ni une coordonnée. Cet exemple ne met pas à jour et ne dessine pas l’objet ; aucun personnage ne se déplace donc à l’écran.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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();  }
}
Enregistrer une valeur de diagnostic en RAM fc_debug.c

Objectif : Conserve une valeur de jeu nommée dans un tampon de diagnostic, indépendamment de l’affichage.

Fonctionnement : Le programme initialise le débogage, fixe l’étiquette d’image à 1 et enregistre la valeur 42 sous le nom HP.

Commandes et résultat attendu : 001 est le nombre d’entrées de trace. HP=42 est stocké dans le tampon de diagnostic ; l’écran seul ne montre ni ne vérifie ce contenu.

Enregistrer une valeur de diagnostic en RAM : 001 est le nombre d’entrées de trace. HP=42 est stocké dans le tampon de diagnostic ; l’écran seul ne montre ni ne vérifie ce contenu.
Exemple d’affichage : 001 est le nombre d’entrées de trace. HP=42 est stocké dans le tampon de diagnostic ; l’écran seul ne montre ni ne vérifie ce contenu.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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();  }
}
Stocker un historique de positions fc_chain.c

Objectif : Montre comment stocker un historique de positions dans un tampon circulaire.

Fonctionnement : chain_init associe un tableau pouvant contenir quatre points. chain_push_head enregistre ensuite (20,30).

Commandes et résultat attendu : 042 est un repère fixe de fin, pas une coordonnée ni un compteur. Examinez trail et points pour vérifier l’historique stocké. Ce programme ne dessine pas de corps suiveur.

Stocker un historique de positions : 042 est un repère fixe de fin, pas une coordonnée ni un compteur. Examinez trail et points pour vérifier l’historique stocké. Ce programme ne dessine pas de corps suiveur.
Exemple d’affichage : 042 est un repère fixe de fin, pas une coordonnée ni un compteur. Examinez trail et points pour vérifier l’historique stocké. Ce programme ne dessine pas de corps suiveur.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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();  }
}
Lire le compteur d’images de la bibliothèque fc_system.c

Objectif : Montre l’effet des attentes comptées en images sur le compteur d’images de la bibliothèque.

Fonctionnement : Après system_init, le programme appelle deux fois system_wait_vblank et affiche system_get_frame8.

Commandes et résultat attendu : Le résultat est 002. La boucle d’attente utilise la fonction utilitaire m_wait du manuel ; la valeur affichée ne continue donc pas à augmenter.

Lire le compteur d’images de la bibliothèque : Le résultat est 002. La boucle d’attente utilise la fonction utilitaire m_wait du manuel ; la valeur affichée ne continue donc pas à augmenter.
Exemple d’affichage : Le résultat est 002. La boucle d’attente utilise la fonction utilitaire m_wait du manuel ; la valeur affichée ne continue donc pas à augmenter.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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();  }
}
Sélectionner l’ID de la scène courante fc_scene.c

Objectif : Montre comment sélectionner un ID pour distinguer des scènes, comme le titre et la partie en cours.

Fonctionnement : Le programme enregistre deux entrées SceneDef initialisées à zéro et sélectionne l’ID 1 avec scene_change.

Commandes et résultat attendu : 001 est l’ID courant renvoyé par scene_get_current. Cet exemple n’enregistre ni callbacks de scène ni dessin de transition.

Sélectionner l’ID de la scène courante : 001 est l’ID courant renvoyé par scene_get_current. Cet exemple n’enregistre ni callbacks de scène ni dessin de transition.
Exemple d’affichage : 001 est l’ID courant renvoyé par scene_get_current. Cet exemple n’enregistre ni callbacks de scène ni dessin de transition.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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();  }
}
Cumuler un déplacement fractionnaire Q5.3 fc_subpixel.c

Objectif : Conserve le déplacement inférieur au pixel sous forme de reste et reporte les pixels entiers dans la position.

Fonctionnement : À partir de X=40, la boucle ajoute huit fois 2/8 de pixel. Un décalage à droite extrait les pixels entiers ; un masque conserve le reste.

Commandes et résultat attendu : le déplacement total est de deux pixels, donc X=42 s’affiche sous la forme 042. Cet exemple d’intégration manuelle traite les déplacements positifs ; les négatifs nécessitent une gestion correspondante de l’emprunt.

Cumuler un déplacement fractionnaire Q5.3 : le déplacement total est de deux pixels, donc X=42 s’affiche sous la forme 042. Cet exemple d’intégration manuelle traite les déplacements positifs ; les négatifs nécessitent une gestion correspondante de l’emprunt.
Exemple d’affichage : le déplacement total est de deux pixels, donc X=42 s’affiche sous la forme 042. Cet exemple d’intégration manuelle traite les déplacements positifs ; les négatifs nécessitent une gestion correspondante de l’emprunt.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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();  }
}
Faire défiler le fond NES fc_scroll.c

Objectif : Déplace le fond en modifiant la position de défilement du PPU.

Fonctionnement : À partir de __scroll_set(0,0), le programme incrémente phase une fois par image et l’utilise comme position horizontale.

Commandes et résultat attendu : Le mot SCROLL change de position horizontale. phase revient de 255 à 0. Ce n’est pas un exemple de grande carte couvrant plusieurs tables de noms.

Faire défiler le fond NES : Le mot SCROLL change de position horizontale. phase revient de 255 à 0. Ce n’est pas un exemple de grande carte couvrant plusieurs tables de noms.
Exemple d’affichage : Le mot SCROLL change de position horizontale. phase revient de 255 à 0. Ce n’est pas un exemple de grande carte couvrant plusieurs tables de noms.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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); }
}
Appliquer une mise à jour de tuile par NMI fc_vram_queue.c

Objectif : Soumet une mise à jour PPU depuis le programme principal pour que le traitement NMI l’applique.

Fonctionnement : __vramq_put met en file la tuile 52 à l’adresse de table de noms 0x2103, puis __vramq_commit la soumet. Cette adresse correspond à la coordonnée de tuile (3,8).

Commandes et résultat attendu : Un seul 4 apparaît à la coordonnée du fond (3,8). La tuile 52 est le chiffre 4 dans la police fournie.

Appliquer une mise à jour de tuile par NMI : un seul 4 apparaît à la coordonnée du fond (3,8). La tuile 52 est le chiffre 4 dans la police fournie.
Exemple d’affichage : Un seul 4 apparaît à la coordonnée du fond (3,8). La tuile 52 est le chiffre 4 dans la police fournie.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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();  }
}
Afficher un sprite OAM NES fc_sprite.c

Objectif : Prépare les couleurs des sprites et les données OAM pour dessiner une image indépendamment du fond.

Fonctionnement : Le programme charge la palette des sprites, règle l’entrée 0 sur la position (70,80), la tuile 65 et les attributs 0, active le rendu et répète le DMA OAM.

Commandes et résultat attendu : Une lettre A immobile apparaît comme sprite. L’exemple montre la configuration et le transfert OAM, pas une animation.

Afficher un sprite OAM NES : une lettre A immobile apparaît sous forme de sprite. L’exemple illustre la configuration et le transfert OAM, pas une animation.
Exemple d’affichage : Une lettre A immobile apparaît comme sprite. L’exemple montre la configuration et le transfert OAM, pas une animation.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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(); }
}
Jouer un son pulsé avec l’APU fc_sound.c

Objectif : Règle les valeurs de contrôle, de temporisation et de longueur du canal pulsé intégré de la NES.

Fonctionnement : Après nes_apu_init, le programme appelle nes_sfx_square1(0xBF,400,20). Le contrôle 0xBF choisit un volume constant et arrête le compteur de longueur ; le son se prolonge donc. 20 est un indice de table de longueurs, pas une durée de 20 images.

Commandes et résultat attendu : Un son pulsé soutenu joue pendant l’affichage de 042. Appelez nes_apu_silence_all pour l’arrêter. Le repère affiché seul ne vérifie pas la sortie sonore.

Jouer un son pulsé avec l’APU : Un son pulsé soutenu joue pendant l’affichage de 042. Appelez nes_apu_silence_all pour l’arrêter. Le repère affiché seul ne vérifie pas la sortie sonore.
Exemple d’affichage : Un son pulsé soutenu joue pendant l’affichage de 042. Appelez nes_apu_silence_all pour l’arrêter. Le repère affiché seul ne vérifie pas la sortie sonore.

Télécharger le source / État de la vérification

powershell
.\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
c
// 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();  }
}

Dictionnaire des fonctions intrinsèques

161 entrées. Ouvrez une entrée pour consulter sa syntaxe, ses arguments, les notes source et son exemple. Les fragments d'appel nécessitent une initialisation autour du code ; ce ne sont pas des ROM autonomes. Les exemples montrent comment transmettre des valeurs préparées à l'API, sans constituer une validation sur le périphérique.

audio_vblank.h — Lecture musicale par interruption VBlank

La musique à quatre canaux avance dans la NMI. Initialisez le pilote, associez un morceau et préremplissez la file, puis activez la NMI et poursuivez le remplissage depuis le programme principal. La NMI lit une file RAM : une petite quantité de RAM suffit pour un long flux, à condition de la réalimenter à temps. Chaque canal dispose de commandes HOLD/STOP indépendantes.

Un enregistrement contient cinq octets : delay, CH1, CH2, CH3, CH4. Une durée de 1 à 255 indique le nombre de ticks NMI pendant lesquels le nouvel état reste actif. Zéro termine la lecture et les quatre autres octets sont ignorés. Les notes de 0 à 71 correspondent à C2–B7 (24=C4). Pour le bruit, les valeurs 0 à 15 sélectionnent une période en mode long, et 16 à 31 le mode court. Sur tous les canaux, 255=NES_AUDIO_HOLD conserve l’état et 254=NES_AUDIO_STOP coupe le son.

Ce pilote à quatre canaux est réglé pour le NTSC : CH1=impulsion 1, CH2=impulsion 2, CH3=triangle et CH4=bruit, avec des indices de 0 à 3. Il ne gère ni le DMC ni les extensions audio. Ne l’utilisez pas en parallèle avec du code écrivant directement dans l’APU, comme les effets de audio.c. Il ne fournit ni table de fréquences PAL ni correction de tempo pour le PAL.

Si la durée expire sans nouvel enregistrement BGM, les notes BGM s’arrêtent ; la lecture reprend lors d’une NMI après l’arrivée de données. Un effet sonore indépendant continue. Le compteur mesure les entrées en manque de données, pas chaque image passée à attendre. Insérer uniquement des valeurs HOLD ne rétablit pas les anciennes notes BGM. En général, remplissez la file à chaque image et préparez une durée de lecture suffisante avant un traitement long.

fds.h — Opérations sur disque FDS

__fds_available

Indique si la cible de compilation est FDS, par exemple pour afficher des informations propres à la cible dans du code de jeu partagé.

Syntaxe

c
u8 __fds_available(void);

Valeur renvoyée

1 pour une compilation FDS, sinon 0. Il ne s’agit pas d’une détection de périphérique à l’exécution.

Conditions et limites

Cette requête est valide pour les cibles cartouche comme NROM. Placer une API réservée au FDS dans if (__fds_available()) ne la dispense pas des vérifications de cible ; organisez le code source selon la cible.

La capture utilise le chargement direct des fichiers de démarrage du disque en RAM par KUROSAKI. Cet exemple n’appelle aucune routine BIOS et ne vérifie ni les transferts de fichiers, ni le changement de disque, ni le démarrage sur matériel réel. Le micrologiciel de test vide n’est pas un BIOS de remplacement. L’exemple utilise la NMI pour l’affichage et désactive les IRQ masquables, inutiles à ces requêtes.

L’exemple de compilation produit un .fds à disposition native, sans les fichiers de démarrage supplémentaires de l’écran d’approbation. Cette configuration sert à vérifier le démarrage direct de KUROSAKI ; la procédure de démarrage d’une disquette sur matériel réel doit être validée séparément.

Exemple d’utilisation

c
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(); }
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

La version FDS affiche 001 et la version NROM affiche 000. Le même code C renvoie une valeur différente selon la cible de compilation, indépendamment de la présence d’un disque.

__fds_available: AVAILABLE-FDS-DEFAULT — La version FDS affiche 001 et la version NROM affiche 000. Le même code C renvoie une valeur différente selon la cible de compilation, indépendamment de la présence d’un disque.
AVAILABLE-FDS-DEFAULT — La version FDS affiche 001 et la version NROM affiche 000. Le même code C renvoie une valeur différente selon la cible de compilation, indépendamment de la présence d’un disque.
__fds_available: AVAILABLE-NROM-DEFAULT — La version FDS affiche 001 et la version NROM affiche 000. Le même code C renvoie une valeur différente selon la cible de compilation, indépendamment de la présence d’un disque.
AVAILABLE-NROM-DEFAULT — La version FDS affiche 001 et la version NROM affiche 000. Le même code C renvoie une valeur différente selon la cible de compilation, indépendamment de la présence d’un disque.

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/fds.h:7

Notes de déclaration (texte source)
Return the compile-time FDS mapper selection as 0 or 1; this is not a hardware probe.

kitaqfc/kitaqfc/CodeGenerator.cs:5146

csharp
if (funcName == "__fds_available")
            {
                if (!Need(0)) return true;
                EmitAsm("LDA", Imm(isFds ? 1 : 0));
                return true;
            }
__fds_disk_ready

Vérifie immédiatement la présence du disque, permettant à un écran d’insertion de l’interroger une fois par image sans bloquer.

Syntaxe

c
u8 __fds_disk_ready(void);

Valeur renvoyée

Renvoie 1 lorsque le bit 0 de $4032 est à zéro, et 0 lorsqu’il est à un.

Conditions et limites

Compilez avec --mapper=fds. Pour un mapper de cartouche, appeler une fonction réservée au FDS autre que __fds_available est refusé avec KQFC2420.

Le test utilise le bit 0 de $4032, qui indique la présence du disque. Il ne vérifie ni la disponibilité du lecteur signalée par le bit 1, ni l’existence d’un fichier, ni la fin d’un chargement. Le mot « ready » ne signifie pas ici qu’un transfert peut commencer.

KUROSAKI utilise un modèle partiel du contrôleur FDS. Cet exemple observe une présence de 1, un masque de protection de 0 et un statut de 0 ; la capture ne permet pas d’établir le comportement en cas d’éjection, de support protégé ou d’erreur CRC.

La capture utilise le chargement direct des fichiers de démarrage du disque en RAM par KUROSAKI. Cet exemple n’appelle aucune routine BIOS et ne vérifie ni les transferts de fichiers, ni le changement de disque, ni le démarrage sur matériel réel. Le micrologiciel de test vide n’est pas un BIOS de remplacement. L’exemple utilise la NMI pour l’affichage et désactive les IRQ masquables, inutiles à ces requêtes.

L’exemple de compilation produit un .fds à disposition native, sans les fichiers de démarrage supplémentaires de l’écran d’approbation. Cette configuration sert à vérifier le démarrage direct de KUROSAKI ; la procédure de démarrage d’une disquette sur matériel réel doit être validée séparément.

NESdev : registres matériels FDS

Exemple d’utilisation

c
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(); }
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Sous PRESENT / PROTECT / STATUS, l’écran affiche 1 / 0 / 0, puis 2 sous COMPLETED PRESENCE WAITS. Les deux attentes de présence sont revenues ; ce n’est pas un indicateur de réussite d’un transfert de fichier. NO FILE TRANSFER TESTED apparaît en dessous.

__fds_disk_ready: STATUS-FDS-DEFAULT — Sous PRESENT / PROTECT / STATUS, l’écran affiche 1 / 0 / 0, puis 2 sous COMPLETED PRESENCE WAITS. Les deux attentes de présence sont revenues ; ce n’est pas un indicateur de réussite d’un transfert de fichier. NO FILE TRANSFER TESTED apparaît en dessous.
STATUS-FDS-DEFAULT — Sous PRESENT / PROTECT / STATUS, l’écran affiche 1 / 0 / 0, puis 2 sous COMPLETED PRESENCE WAITS. Les deux attentes de présence sont revenues ; ce n’est pas un indicateur de réussite d’un transfert de fichier. NO FILE TRANSFER TESTED apparaît en dessous.

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/fds.h:9

Notes de déclaration (texte source)
Return the inverted disk-absent bit from 4032 as 0 or 1; this checks presence, not transfer completion.

kitaqfc/kitaqfc/CodeGenerator.cs:5160

csharp
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

Lit le registre d’état matériel FDS à $4030. Ce n’est pas une erreur mémorisée de la dernière opération disque.

Syntaxe

c
u8 __fds_error(void);

Valeur renvoyée

Renvoie la valeur complète sur huit bits de $4030. Conservez le résultat de __fds_load_file() ou __fds_save_file() pour traiter les codes d’erreur BIOS.

Conditions et limites

Compilez avec --mapper=fds. Pour un mapper de cartouche, appeler une fonction réservée au FDS autre que __fds_available est refusé avec KQFC2420.

KUROSAKI utilise un modèle partiel du contrôleur FDS. Cet exemple observe une présence de 1, un masque de protection de 0 et un statut de 0 ; la capture ne permet pas d’établir le comportement en cas d’éjection, de support protégé ou d’erreur CRC.

Lire le statut acquitte aussi les demandes d’interruption. Évitez les interrogations indiscriminées si un autre gestionnaire dépend du même statut. KUROSAKI efface son indicateur d’IRQ FDS en attente lors de cette lecture.

La capture utilise le chargement direct des fichiers de démarrage du disque en RAM par KUROSAKI. Cet exemple n’appelle aucune routine BIOS et ne vérifie ni les transferts de fichiers, ni le changement de disque, ni le démarrage sur matériel réel. Le micrologiciel de test vide n’est pas un BIOS de remplacement. L’exemple utilise la NMI pour l’affichage et désactive les IRQ masquables, inutiles à ces requêtes.

L’exemple de compilation produit un .fds à disposition native, sans les fichiers de démarrage supplémentaires de l’écran d’approbation. Cette configuration sert à vérifier le démarrage direct de KUROSAKI ; la procédure de démarrage d’une disquette sur matériel réel doit être validée séparément.

NESdev : registres matériels FDS

Exemple d’utilisation

c
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(); }
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Sous PRESENT / PROTECT / STATUS, l’écran affiche 1 / 0 / 0, puis 2 sous COMPLETED PRESENCE WAITS. Les deux attentes de présence sont revenues ; ce n’est pas un indicateur de réussite d’un transfert de fichier. NO FILE TRANSFER TESTED apparaît en dessous.

__fds_error: STATUS-FDS-DEFAULT — Sous PRESENT / PROTECT / STATUS, l’écran affiche 1 / 0 / 0, puis 2 sous COMPLETED PRESENCE WAITS. Les deux attentes de présence sont revenues ; ce n’est pas un indicateur de réussite d’un transfert de fichier. NO FILE TRANSFER TESTED apparaît en dessous.
STATUS-FDS-DEFAULT — Sous PRESENT / PROTECT / STATUS, l’écran affiche 1 / 0 / 0, puis 2 sous COMPLETED PRESENCE WAITS. Les deux attentes de présence sont revenues ; ce n’est pas un indicateur de réussite d’un transfert de fichier. NO FILE TRANSFER TESTED apparaît en dessous.

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/fds.h:13

Notes de déclaration (texte source)
Read the entire 4030 status register; this is not a stored BIOS error code.

kitaqfc/kitaqfc/CodeGenerator.cs:5173

csharp
case "__fds_error":
                    if (!Need(0)) return true;
                    EmitAsm("LDA", Mem(0x4030));
                    return true;
__fds_side

Renvoie le bit de protection en écriture masqué de $4032. Cette fonction n’identifie pas la face A ou B du disque.

Syntaxe

c
u8 __fds_side(void);

Valeur renvoyée

Renvoie $4032 & 0x04, soit 0 ou 4, et non un booléen normalisé. Le bit matériel 2 indique une protection en écriture ou un disque éjecté.

Conditions et limites

Compilez avec --mapper=fds. Pour un mapper de cartouche, appeler une fonction réservée au FDS autre que __fds_available est refusé avec KQFC2420.

KUROSAKI utilise un modèle partiel du contrôleur FDS. Cet exemple observe une présence de 1, un masque de protection de 0 et un statut de 0 ; la capture ne permet pas d’établir le comportement en cas d’éjection, de support protégé ou d’erreur CRC.

Pour tester la protection, utilisez (__fds_side() != 0). La présence du disque et le résultat de l’opération BIOS sont aussi nécessaires pour déterminer si la sauvegarde réussit.

La capture utilise le chargement direct des fichiers de démarrage du disque en RAM par KUROSAKI. Cet exemple n’appelle aucune routine BIOS et ne vérifie ni les transferts de fichiers, ni le changement de disque, ni le démarrage sur matériel réel. Le micrologiciel de test vide n’est pas un BIOS de remplacement. L’exemple utilise la NMI pour l’affichage et désactive les IRQ masquables, inutiles à ces requêtes.

L’exemple de compilation produit un .fds à disposition native, sans les fichiers de démarrage supplémentaires de l’écran d’approbation. Cette configuration sert à vérifier le démarrage direct de KUROSAKI ; la procédure de démarrage d’une disquette sur matériel réel doit être validée séparément.

NESdev : registres matériels FDS

Exemple d’utilisation

c
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(); }
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Sous PRESENT / PROTECT / STATUS, l’écran affiche 1 / 0 / 0, puis 2 sous COMPLETED PRESENCE WAITS. Les deux attentes de présence sont revenues ; ce n’est pas un indicateur de réussite d’un transfert de fichier. NO FILE TRANSFER TESTED apparaît en dessous.

__fds_side: STATUS-FDS-DEFAULT — Sous PRESENT / PROTECT / STATUS, l’écran affiche 1 / 0 / 0, puis 2 sous COMPLETED PRESENCE WAITS. Les deux attentes de présence sont revenues ; ce n’est pas un indicateur de réussite d’un transfert de fichier. NO FILE TRANSFER TESTED apparaît en dessous.
STATUS-FDS-DEFAULT — Sous PRESENT / PROTECT / STATUS, l’écran affiche 1 / 0 / 0, puis 2 sous COMPLETED PRESENCE WAITS. Les deux attentes de présence sont revenues ; ce n’est pas un indicateur de réussite d’un transfert de fichier. NO FILE TRANSFER TESTED apparaît en dessous.

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/fds.h:11

Notes de déclaration (texte source)
Return raw status mask 4032 & 04, either 0 or 4. This is not a numbered disk-side identifier.

kitaqfc/kitaqfc/CodeGenerator.cs:5167

csharp
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

Exprime une attente d’insertion de disque. Appelle la même boucle de détection de présence que __fds_wait_ready().

Syntaxe

c
void __fds_wait_insert(void);

Valeur renvoyée

Aucune valeur de retour.

Conditions et limites

Compilez avec --mapper=fds. Pour un mapper de cartouche, appeler une fonction réservée au FDS autre que __fds_available est refusé avec KQFC2420.

Le test utilise le bit 0 de $4032, qui indique la présence du disque. Il ne vérifie ni la disponibilité du lecteur signalée par le bit 1, ni l’existence d’un fichier, ni la fin d’un chargement. Le mot « ready » ne signifie pas ici qu’un transfert peut commencer.

Il n’y a pas de délai d’expiration. Sans disque, l’appel reste bloqué. Interrogez __fds_disk_ready() une fois par image si le jeu doit continuer à mettre l’affichage à jour ou à accepter des entrées. L’exemple vérifie la présence avant chacune des deux attentes. Un disque déjà inséré suffit à terminer l’attente, sans nouveau cycle de retrait et d’insertion.

KUROSAKI utilise un modèle partiel du contrôleur FDS. Cet exemple observe une présence de 1, un masque de protection de 0 et un statut de 0 ; la capture ne permet pas d’établir le comportement en cas d’éjection, de support protégé ou d’erreur CRC.

La capture utilise le chargement direct des fichiers de démarrage du disque en RAM par KUROSAKI. Cet exemple n’appelle aucune routine BIOS et ne vérifie ni les transferts de fichiers, ni le changement de disque, ni le démarrage sur matériel réel. Le micrologiciel de test vide n’est pas un BIOS de remplacement. L’exemple utilise la NMI pour l’affichage et désactive les IRQ masquables, inutiles à ces requêtes.

L’exemple de compilation produit un .fds à disposition native, sans les fichiers de démarrage supplémentaires de l’écran d’approbation. Cette configuration sert à vérifier le démarrage direct de KUROSAKI ; la procédure de démarrage d’une disquette sur matériel réel doit être validée séparément.

NESdev : registres matériels FDS

Exemple d’utilisation

c
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(); }
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Sous PRESENT / PROTECT / STATUS, l’écran affiche 1 / 0 / 0, puis 2 sous COMPLETED PRESENCE WAITS. Les deux attentes de présence sont revenues ; ce n’est pas un indicateur de réussite d’un transfert de fichier. NO FILE TRANSFER TESTED apparaît en dessous.

__fds_wait_insert: STATUS-FDS-DEFAULT — Sous PRESENT / PROTECT / STATUS, l’écran affiche 1 / 0 / 0, puis 2 sous COMPLETED PRESENCE WAITS. Les deux attentes de présence sont revenues ; ce n’est pas un indicateur de réussite d’un transfert de fichier. NO FILE TRANSFER TESTED apparaît en dessous.
STATUS-FDS-DEFAULT — Sous PRESENT / PROTECT / STATUS, l’écran affiche 1 / 0 / 0, puis 2 sous COMPLETED PRESENCE WAITS. Les deux attentes de présence sont revenues ; ce n’est pas un indicateur de réussite d’un transfert de fichier. NO FILE TRANSFER TESTED apparaît en dessous.

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/fds.h:17

Notes de déclaration (texte source)
Alias the same disk-presence wait as __fds_wait_ready; the call can block indefinitely.

kitaqfc/kitaqfc/CodeGenerator.cs:9148

csharp
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

Attend dans une boucle d’interrogation CPU jusqu’à la présence d’un disque.

Syntaxe

c
void __fds_wait_ready(void);

Valeur renvoyée

Aucune valeur de retour.

Conditions et limites

Compilez avec --mapper=fds. Pour un mapper de cartouche, appeler une fonction réservée au FDS autre que __fds_available est refusé avec KQFC2420.

Le test utilise le bit 0 de $4032, qui indique la présence du disque. Il ne vérifie ni la disponibilité du lecteur signalée par le bit 1, ni l’existence d’un fichier, ni la fin d’un chargement. Le mot « ready » ne signifie pas ici qu’un transfert peut commencer.

Il n’y a pas de délai d’expiration. Sans disque, l’appel reste bloqué. Interrogez __fds_disk_ready() une fois par image si le jeu doit continuer à mettre l’affichage à jour ou à accepter des entrées. L’exemple vérifie la présence avant chacune des deux attentes. Un disque déjà inséré suffit à terminer l’attente, sans nouveau cycle de retrait et d’insertion.

KUROSAKI utilise un modèle partiel du contrôleur FDS. Cet exemple observe une présence de 1, un masque de protection de 0 et un statut de 0 ; la capture ne permet pas d’établir le comportement en cas d’éjection, de support protégé ou d’erreur CRC.

La capture utilise le chargement direct des fichiers de démarrage du disque en RAM par KUROSAKI. Cet exemple n’appelle aucune routine BIOS et ne vérifie ni les transferts de fichiers, ni le changement de disque, ni le démarrage sur matériel réel. Le micrologiciel de test vide n’est pas un BIOS de remplacement. L’exemple utilise la NMI pour l’affichage et désactive les IRQ masquables, inutiles à ces requêtes.

L’exemple de compilation produit un .fds à disposition native, sans les fichiers de démarrage supplémentaires de l’écran d’approbation. Cette configuration sert à vérifier le démarrage direct de KUROSAKI ; la procédure de démarrage d’une disquette sur matériel réel doit être validée séparément.

NESdev : registres matériels FDS

Exemple d’utilisation

c
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(); }
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Sous PRESENT / PROTECT / STATUS, l’écran affiche 1 / 0 / 0, puis 2 sous COMPLETED PRESENCE WAITS. Les deux attentes de présence sont revenues ; ce n’est pas un indicateur de réussite d’un transfert de fichier. NO FILE TRANSFER TESTED apparaît en dessous.

__fds_wait_ready: STATUS-FDS-DEFAULT — Sous PRESENT / PROTECT / STATUS, l’écran affiche 1 / 0 / 0, puis 2 sous COMPLETED PRESENCE WAITS. Les deux attentes de présence sont revenues ; ce n’est pas un indicateur de réussite d’un transfert de fichier. NO FILE TRANSFER TESTED apparaît en dessous.
STATUS-FDS-DEFAULT — Sous PRESENT / PROTECT / STATUS, l’écran affiche 1 / 0 / 0, puis 2 sous COMPLETED PRESENCE WAITS. Les deux attentes de présence sont revenues ; ce n’est pas un indicateur de réussite d’un transfert de fichier. NO FILE TRANSFER TESTED apparaît en dessous.

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/fds.h:15

Notes de déclaration (texte source)
Poll disk presence until the absent bit clears. There is no timeout or cancellation path.

kitaqfc/kitaqfc/CodeGenerator.cs:9148

csharp
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 — Chargement de fichiers FDS

__fds_file_exists

Vérifie si les métadonnées de fichiers compilées contiennent l’ID demandé. Un enregistrement de taille nulle compte tout de même comme présent.

Syntaxe

c
u8 __fds_file_exists(u8 id);

Paramètres

id

Identifiant de fichier à rechercher. Les métadonnées utilisent les ID 0 à 254 ; 255 est réservé au terminateur. Un ID est distinct d’un numéro de fichier, d’un nom de fichier ou d’une adresse de destination.

Valeur renvoyée

1 si la table compilée contient l’ID, sinon 0. Cela ne garantit pas que le disque inséré contient ce fichier.

Conditions et limites

Compilez avec --mapper=fds. Pour un mapper de cartouche, appeler une fonction réservée au FDS autre que __fds_available est refusé avec KQFC2420.

Recherche dans la table compilée depuis --fds-meta=... ou --fds-manifest=..., ainsi que dans les enregistrements d’overlays générés. Cette fonction ne lit pas le répertoire du disque. Un fichier sans entrée dans la table n’est pas trouvé, même s’il figure dans l’image disque. Les enregistrements manuels et générés sont limités à 42 au total.

La taille est la valeur déclarée dans les métadonnées. Elle n’est pas nécessairement égale à la longueur déduite du fichier source, à la taille utile d’un overlay raccourci ou au contenu d’un disque remplacé. Déclarez des tailles exactes si vous utilisez ces requêtes pour borner les accès aux données.

Placez fds_metadata.json et ses deux fichiers .bin à côté du code C. L’ID 16 contient une séquence originale de 513 octets : deux fois 0..255, puis zéro. L’ID 17 a un contenu vide. Tous deux sont des fichiers PRG non destinés au démarrage (type 0), et cette leçon ne les charge pas. Les chemins source relatifs sont résolus depuis le dossier du manifeste.

La capture utilise le chargement direct des fichiers de démarrage du disque en RAM par KUROSAKI. Cet exemple n’appelle aucune routine BIOS et ne vérifie ni les transferts de fichiers, ni le changement de disque, ni le démarrage sur matériel réel. Le micrologiciel de test vide n’est pas un BIOS de remplacement. L’exemple utilise la NMI pour l’affichage et désactive les IRQ masquables, inutiles à ces requêtes.

L’exemple de compilation produit un .fds à disposition native, sans les fichiers de démarrage supplémentaires de l’écran d’approbation. Cette configuration sert à vérifier le démarrage direct de KUROSAKI ; la procédure de démarrage d’une disquette sur matériel réel doit être validée séparément.

Exemple d’utilisation

c
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(); }
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le tableau indique que l’ID 16 existe avec 513 octets, que l’ID 17 existe avec zéro octet et que l’ID 18 est absent avec zéro octet. TABLE VALUES MATCH: PASS compare les valeurs obtenues à cette combinaison. Une taille nulle ne suffit pas à distinguer un enregistrement vide d’un ID absent.

__fds_file_exists: METADATA-FDS-DEFAULT — Le tableau indique que l’ID 16 existe avec 513 octets, que l’ID 17 existe avec zéro octet et que l’ID 18 est absent avec zéro octet. TABLE VALUES MATCH: PASS compare les valeurs obtenues à cette combinaison. Une taille nulle ne suffit pas à distinguer un enregistrement vide d’un ID absent.
METADATA-FDS-DEFAULT — Le tableau indique que l’ID 16 existe avec 513 octets, que l’ID 17 existe avec zéro octet et que l’ID 18 est absent avec zéro octet. TABLE VALUES MATCH: PASS compare les valeurs obtenues à cette combinaison. Une taille nulle ne suffit pas à distinguer un enregistrement vide d’un ID absent.

Programme d’exemple complet

Commande de compilation

powershell
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.json
Implémentation et source

kitaqfc/lib/fds_file.h:18

Notes de déclaration (texte source)
Query the compiled --fds-meta/--fds-manifest table, not the inserted disk; return 0 or 1.

kitaqfc/kitaqfc/CodeGenerator.cs:9481

csharp
EmitHelperStart("__fds_file_exists");
            EmitFdsMetadataSearch(foundReturnsSize: false);

            // Return the compiled metadata size, using zero for an unmatched ID.
__fds_file_size

Renvoie la taille en octets déclarée pour un ID dans les métadonnées compilées, par exemple pour consulter la taille de chargement prévue d’une ressource.

Syntaxe

c
u16 __fds_file_size(u8 id);

Paramètres

id

Identifiant de fichier à rechercher. Les métadonnées utilisent les ID 0 à 254 ; 255 est réservé au terminateur. Un ID est distinct d’un numéro de fichier, d’un nom de fichier ou d’une adresse de destination.

Valeur renvoyée

Taille déclarée non signée sur 16 bits, de 0 à 65535. Les ID absents renvoient aussi zéro ; utilisez __fds_file_exists() si cette distinction est nécessaire.

Conditions et limites

Compilez avec --mapper=fds. Pour un mapper de cartouche, appeler une fonction réservée au FDS autre que __fds_available est refusé avec KQFC2420.

Recherche dans la table compilée depuis --fds-meta=... ou --fds-manifest=..., ainsi que dans les enregistrements d’overlays générés. Cette fonction ne lit pas le répertoire du disque. Un fichier sans entrée dans la table n’est pas trouvé, même s’il figure dans l’image disque. Les enregistrements manuels et générés sont limités à 42 au total.

La taille est la valeur déclarée dans les métadonnées. Elle n’est pas nécessairement égale à la longueur déduite du fichier source, à la taille utile d’un overlay raccourci ou au contenu d’un disque remplacé. Déclarez des tailles exactes si vous utilisez ces requêtes pour borner les accès aux données.

Placez fds_metadata.json et ses deux fichiers .bin à côté du code C. L’ID 16 contient une séquence originale de 513 octets : deux fois 0..255, puis zéro. L’ID 17 a un contenu vide. Tous deux sont des fichiers PRG non destinés au démarrage (type 0), et cette leçon ne les charge pas. Les chemins source relatifs sont résolus depuis le dossier du manifeste.

La capture utilise le chargement direct des fichiers de démarrage du disque en RAM par KUROSAKI. Cet exemple n’appelle aucune routine BIOS et ne vérifie ni les transferts de fichiers, ni le changement de disque, ni le démarrage sur matériel réel. Le micrologiciel de test vide n’est pas un BIOS de remplacement. L’exemple utilise la NMI pour l’affichage et désactive les IRQ masquables, inutiles à ces requêtes.

L’exemple de compilation produit un .fds à disposition native, sans les fichiers de démarrage supplémentaires de l’écran d’approbation. Cette configuration sert à vérifier le démarrage direct de KUROSAKI ; la procédure de démarrage d’une disquette sur matériel réel doit être validée séparément.

Exemple d’utilisation

c
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(); }
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le tableau indique que l’ID 16 existe avec 513 octets, que l’ID 17 existe avec zéro octet et que l’ID 18 est absent avec zéro octet. TABLE VALUES MATCH: PASS compare les valeurs obtenues à cette combinaison. Une taille nulle ne suffit pas à distinguer un enregistrement vide d’un ID absent.

__fds_file_size: METADATA-FDS-DEFAULT — Le tableau indique que l’ID 16 existe avec 513 octets, que l’ID 17 existe avec zéro octet et que l’ID 18 est absent avec zéro octet. TABLE VALUES MATCH: PASS compare les valeurs obtenues à cette combinaison. Une taille nulle ne suffit pas à distinguer un enregistrement vide d’un ID absent.
METADATA-FDS-DEFAULT — Le tableau indique que l’ID 16 existe avec 513 octets, que l’ID 17 existe avec zéro octet et que l’ID 18 est absent avec zéro octet. TABLE VALUES MATCH: PASS compare les valeurs obtenues à cette combinaison. Une taille nulle ne suffit pas à distinguer un enregistrement vide d’un ID absent.

Programme d’exemple complet

Commande de compilation

powershell
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.json
Implémentation et source

kitaqfc/lib/fds_file.h:20

Notes de déclaration (texte source)
Return the compiled metadata size, or zero if id is absent. No disk directory is read.

kitaqfc/kitaqfc/CodeGenerator.cs:9485

csharp
EmitHelperStart("__fds_file_size");
            EmitFdsMetadataSearch(foundReturnsSize: true);
__fds_load_file

Lit un fichier via le BIOS et copie ses données PRG dans un tampon CPU. Utilisez cette fonction pour charger des sauvegardes ou des ressources de niveau dans la mémoire réservée par le jeu.

Syntaxe

c
u8 __fds_load_file(u8 id, u8* dst);

Paramètres

id

Identifiant d’un fichier non destiné au démarrage, inclus dans le disque généré, de 0 à 254 ; l’exemple utilise 73. Il est distinct de la position physique du fichier. L’ID 255, les identifiants absents et les fichiers de démarrage sont refusés.

dst

Destination d’un fichier PRG, avec assez de place pour toute sa longueur empaquetée. Passez zéro pour laisser les données à la destination définie dans l’en-tête, sans copie. Pour les fichiers PPU CHR ou de table de noms, seul zéro est accepté ; le BIOS transfère vers la destination PPU native.

Valeur renvoyée

Zéro en cas de succès. Les erreurs BIOS non nulles sont renvoyées telles quelles. Si le BIOS annonce un succès sans avoir chargé exactement un fichier, le résultat est 64 ($40). Un identifiant, un tampon ou une combinaison type de fichier/destination invalide renvoie 255 ($FF).

Conditions et limites

Utilisez cette API avec --mapper=fds --fds-layout=fds32. Le manifeste --fds-meta définit les identifiants de fichiers, les adresses de chargement, les types, le statut de démarrage et l’ordre. Les adresses et tailles de transfert proviennent de l’image disque produite par la compilation ; cette API n’inspecte pas un disque quelconque inséré.

Le BIOS charge d’abord à l’adresse stockée dans l’en-tête du disque, puis cette fonction copie vers dst. Elle choisit le sens de copie pour préserver les données en cas de chevauchement, mais la zone intermédiaire doit également être réservée. Un transfert échoué ne copie rien vers dst. Après un appel BIOS, la banque résidente est marquée 255 (inconnue).

Chaque tampon CPU doit tenir entièrement dans $0200–$07FF ou dans $6000–$DFFF. Les plages qui bouclent dans l’espace d’adressage et les destinations dans les registres d’E/S sont refusées. L’appelant doit aussi éviter tout chevauchement avec le code, l’OAM, les autres variables et les zones temporaires du compilateur. L’exemple utilise la banque 0 pour le code commun, $8000 pour la zone intermédiaire et deux tampons de quatre octets à $8500 et $8600.

Ces appels BIOS sont synchrones. Appelez-les depuis le code commun pour que le transfert n’écrase ni le code en cours d’exécution ni son adresse de retour ; ne les appelez pas depuis un gestionnaire d’interruption. Respectez les prérequis du BIOS pendant les E/S disque : arrêtez le rendu et la NMI, réglez l’incrément d’adresse PPU à un et désactivez les sources d’IRQ autres que le disque. La continuité du jeu ou de la musique pendant le transfert n’est pas garantie.

La capture utilise un programme de test original de la convention d’appel BIOS pour vérifier le code CPU compilé, le contenu des tampons, la sauvegarde suivie d’une relecture et chaque pixel affiché. Elle ne vérifie ni le véritable BIOS, ni les écritures sur disque physique, ni le CRC, ni la récupération après coupure de courant. Une exécution FDS ordinaire dans KUROSAKI ne suffit pas à établir le même comportement de sauvegarde.

Utilisez l’exemple avec fds_file_io_manifest.json et fds_file_io_payload.bin du même dossier. Le vérificateur ci-dessous génère un programme de test temporaire à partir de sources originales, puis le supprime. Aucun BIOS commercial n’est inclus, et ce programme de test n’est pas un BIOS destiné aux jeux ordinaires.

Convention d’appel du BIOS FDS

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

LOAD, SAVE, RELOAD STATUS et BYTE CHECK ERRORS affichent tous 000. INITIAL FIRST/LAST affiche 011 et 044, premier et dernier des quatre octets chargés à $8500. L’exemple les remplace par 7, 14, 21 et 28, les sauvegarde puis les recharge à $8600 : SAVED FIRST / LAST affiche donc 007 et 028. STAGING 8000 / SIZE 4 indique l’adresse de chargement définie sur disque et la longueur du fichier. Le test indépendant vérifie également que l’ID 73 est sauvegardé dans le quatrième fichier physique, à la position 3 en comptant depuis zéro.

__fds_load_file: Configuration par défaut — LOAD, SAVE, RELOAD STATUS et BYTE CHECK ERRORS affichent tous 000. INITIAL FIRST/LAST affiche 011 et 044, premier et dernier des quatre octets chargés à $8500. L’exemple les remplace par 7, 14, 21 et 28, les sauvegarde puis les recharge à $8600 : SAVED FIRST / LAST affiche donc 007 et 028. STAGING 8000 / SIZE 4 indique l’adresse de chargement définie sur disque et la longueur du fichier. Le test indépendant vérifie également que l’ID 73 est sauvegardé dans le quatrième fichier physique, à la position 3 en comptant depuis zéro.
Configuration par défaut — LOAD, SAVE, RELOAD STATUS et BYTE CHECK ERRORS affichent tous 000. INITIAL FIRST/LAST affiche 011 et 044, premier et dernier des quatre octets chargés à $8500. L’exemple les remplace par 7, 14, 21 et 28, les sauvegarde puis les recharge à $8600 : SAVED FIRST / LAST affiche donc 007 et 028. STAGING 8000 / SIZE 4 indique l’adresse de chargement définie sur disque et la longueur du fichier. Le test indépendant vérifie également que l’ID 73 est sauvegardé dans le quatrième fichier physique, à la position 3 en comptant depuis zéro.

Programme d’exemple complet

fds_file_io_manifest.json

fds_file_io_payload.bin

Commande de compilation

powershell
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.py
Implémentation et source

kitaqfc/lib/fds_file.h:11

Notes de déclaration (texte source)
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

csharp
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

Écrase, avec les octets d’un tampon CPU, un emplacement de sauvegarde PRG de taille fixe réservé en fin de face du disque. Le fichier conserve son identifiant, son nom et sa future adresse de chargement.

Syntaxe

c
u8 __fds_save_file(u8 id, const u8* src, u16 len);

Paramètres

id

Identifiant d’un fichier non destiné au démarrage, inclus dans le disque généré, de 0 à 254 ; l’exemple utilise 73. Il est distinct de la position physique du fichier. L’ID 255, les identifiants absents et les fichiers de démarrage sont refusés.

src

Début des données en RAM CPU, lisible sur len octets. Cette API ne sauvegarde pas directement la VRAM.

len

Nombre d’octets à sauvegarder ; il doit être égal à la longueur du fichier empaqueté. Si le jeu a moins de données, remplissez la partie inutilisée du tampon de sauvegarde avant l’appel.

Valeur renvoyée

Zéro en cas de succès ; sinon, l’erreur BIOS, par exemple une protection en écriture ou un échec de vérification après écriture. Renvoie 255 ($FF) sans appeler le BIOS si l’ID est invalide, si le fichier est de démarrage ou non PRG, s’il n’est pas le dernier de sa face, si la longueur diffère ou si la plage CPU est invalide.

Conditions et limites

Utilisez cette API avec --mapper=fds --fds-layout=fds32. Le manifeste --fds-meta définit les identifiants de fichiers, les adresses de chargement, les types, le statut de démarrage et l’ordre. Les adresses et tailles de transfert proviennent de l’image disque produite par la compilation ; cette API n’inspecte pas un disque quelconque inséré.

Le BIOS WriteFile masque logiquement les fichiers qui suivent la position d’écriture. Cette API n’accepte donc que le dernier fichier PRG non destiné au démarrage de chaque face. Elle n’ajoute pas de fichier et ne redimensionne pas l’emplacement. Celui-ci doit rester le dernier dans la disposition finale, y compris après l’ajout des aides au démarrage générées et des overlays automatiques.

Le compilateur distingue l’identifiant de fichier, le numéro d’en-tête et l’ordre physique. L’exemple a l’ID 73 et le numéro de manifeste 200, mais transmet la position 3 au BIOS. Les numéros de disque et de face proviennent aussi de la disposition résolue. N’utilisez pas cette API avec un disque dont la disposition a été modifiée indépendamment du manifeste.

Chaque tampon CPU doit tenir entièrement dans $0200–$07FF ou dans $6000–$DFFF. Les plages qui bouclent dans l’espace d’adressage et les destinations dans les registres d’E/S sont refusées. L’appelant doit aussi éviter tout chevauchement avec le code, l’OAM, les autres variables et les zones temporaires du compilateur. L’exemple utilise la banque 0 pour le code commun, $8000 pour la zone intermédiaire et deux tampons de quatre octets à $8500 et $8600.

Ces appels BIOS sont synchrones. Appelez-les depuis le code commun pour que le transfert n’écrase ni le code en cours d’exécution ni son adresse de retour ; ne les appelez pas depuis un gestionnaire d’interruption. Respectez les prérequis du BIOS pendant les E/S disque : arrêtez le rendu et la NMI, réglez l’incrément d’adresse PPU à un et désactivez les sources d’IRQ autres que le disque. La continuité du jeu ou de la musique pendant le transfert n’est pas garantie.

La capture utilise un programme de test original de la convention d’appel BIOS pour vérifier le code CPU compilé, le contenu des tampons, la sauvegarde suivie d’une relecture et chaque pixel affiché. Elle ne vérifie ni le véritable BIOS, ni les écritures sur disque physique, ni le CRC, ni la récupération après coupure de courant. Une exécution FDS ordinaire dans KUROSAKI ne suffit pas à établir le même comportement de sauvegarde.

Utilisez l’exemple avec fds_file_io_manifest.json et fds_file_io_payload.bin du même dossier. Le vérificateur ci-dessous génère un programme de test temporaire à partir de sources originales, puis le supprime. Aucun BIOS commercial n’est inclus, et ce programme de test n’est pas un BIOS destiné aux jeux ordinaires.

Convention d’appel du BIOS FDS

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

LOAD, SAVE, RELOAD STATUS et BYTE CHECK ERRORS affichent tous 000. INITIAL FIRST/LAST affiche 011 et 044, premier et dernier des quatre octets chargés à $8500. L’exemple les remplace par 7, 14, 21 et 28, les sauvegarde puis les recharge à $8600 : SAVED FIRST / LAST affiche donc 007 et 028. STAGING 8000 / SIZE 4 indique l’adresse de chargement définie sur disque et la longueur du fichier. Le test indépendant vérifie également que l’ID 73 est sauvegardé dans le quatrième fichier physique, à la position 3 en comptant depuis zéro.

__fds_save_file: Configuration par défaut — LOAD, SAVE, RELOAD STATUS et BYTE CHECK ERRORS affichent tous 000. INITIAL FIRST/LAST affiche 011 et 044, premier et dernier des quatre octets chargés à $8500. L’exemple les remplace par 7, 14, 21 et 28, les sauvegarde puis les recharge à $8600 : SAVED FIRST / LAST affiche donc 007 et 028. STAGING 8000 / SIZE 4 indique l’adresse de chargement définie sur disque et la longueur du fichier. Le test indépendant vérifie également que l’ID 73 est sauvegardé dans le quatrième fichier physique, à la position 3 en comptant depuis zéro.
Configuration par défaut — LOAD, SAVE, RELOAD STATUS et BYTE CHECK ERRORS affichent tous 000. INITIAL FIRST/LAST affiche 011 et 044, premier et dernier des quatre octets chargés à $8500. L’exemple les remplace par 7, 14, 21 et 28, les sauvegarde puis les recharge à $8600 : SAVED FIRST / LAST affiche donc 007 et 028. STAGING 8000 / SIZE 4 indique l’adresse de chargement définie sur disque et la longueur du fichier. Le test indépendant vérifie également que l’ID 73 est sauvegardé dans le quatrième fichier physique, à la position 3 en comptant depuis zéro.

Programme d’exemple complet

fds_file_io_manifest.json

fds_file_io_payload.bin

Commande de compilation

powershell
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.py
Implémentation et source

kitaqfc/lib/fds_file.h:16

Notes de déclaration (texte source)
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

csharp
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 — Code d'overlay FDS

__fds_current_bank

Renvoie la banque logique mémorisée comme résidente, sans lire le disque.

Syntaxe

c
u8 __fds_current_bank(void);

Valeur renvoyée

Valeur logicielle de résidence sur huit bits : 1 juste après le démarrage standard, ou 255 si elle est inconnue.

Conditions et limites

Compilez avec --mapper=fds. Pour un mapper de cartouche, appeler une fonction réservée au FDS autre que __fds_available est refusé avec KQFC2420.

Le démarrage FDS standard initialise la banque mémorisée à 1. __fds_load_bank() la met à jour après un chargement réussi. Les chargements bruts via __fds_load_file() ou __fds_load_overlay(), ainsi que les transferts de banque échoués, fixent cette valeur à 255 (inconnue). Aucun contenu mémoire n’est vérifié ; ne vous fiez pas à cette information après modification directe de la fenêtre. Une routine de réinitialisation personnalisée doit gérer elle-même l’initialisation.

La capture utilise le chargement direct des fichiers de démarrage du disque en RAM par KUROSAKI. Cet exemple n’appelle aucune routine BIOS et ne vérifie ni les transferts de fichiers, ni le changement de disque, ni le démarrage sur matériel réel. Le micrologiciel de test vide n’est pas un BIOS de remplacement. L’exemple utilise la NMI pour l’affichage et désactive les IRQ masquables, inutiles à ces requêtes.

L’exemple de compilation produit un .fds à disposition native, sans les fichiers de démarrage supplémentaires de l’écran d’approbation. Cette configuration sert à vérifier le démarrage direct de KUROSAKI ; la procédure de démarrage d’une disquette sur matériel réel doit être validée séparément.

Exemple d’utilisation

c
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(); }
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

CURRENT BANK vaut 1, BANK 1 RESIDENT vaut 1, BANK 2 RESIDENT vaut 0 et OVERLAY FUNCTIONS vaut 2. Placer deux fonctions dans la banque 2 ne rend pas cette banque résidente au démarrage. Dans la compilation de vérification avec --fds-no-overlay-table, seule la dernière valeur devient zéro.

__fds_current_bank: RESIDENCY-FDS-DEFAULT — CURRENT BANK vaut 1, BANK 1 RESIDENT vaut 1, BANK 2 RESIDENT vaut 0 et OVERLAY FUNCTIONS vaut 2. Placer deux fonctions dans la banque 2 ne rend pas cette banque résidente au démarrage. Dans la compilation de vérification avec --fds-no-overlay-table, seule la dernière valeur devient zéro.
RESIDENCY-FDS-DEFAULT — CURRENT BANK vaut 1, BANK 1 RESIDENT vaut 1, BANK 2 RESIDENT vaut 0 et OVERLAY FUNCTIONS vaut 2. Placer deux fonctions dans la banque 2 ne rend pas cette banque résidente au démarrage. Dans la compilation de vérification avec --fds-no-overlay-table, seule la dernière valeur devient zéro.
__fds_current_bank: RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK vaut 1, BANK 1 RESIDENT vaut 1, BANK 2 RESIDENT vaut 0 et OVERLAY FUNCTIONS vaut 2. Placer deux fonctions dans la banque 2 ne rend pas cette banque résidente au démarrage. Dans la compilation de vérification avec --fds-no-overlay-table, seule la dernière valeur devient zéro.
RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK vaut 1, BANK 1 RESIDENT vaut 1, BANK 2 RESIDENT vaut 0 et OVERLAY FUNCTIONS vaut 2. Placer deux fonctions dans la banque 2 ne rend pas cette banque résidente au démarrage. Dans la compilation de vérification avec --fds-no-overlay-table, seule la dernière valeur devient zéro.

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/fds_overlay.h:16

Notes de déclaration (texte source)
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

csharp
case "__fds_current_bank":
                    if (!Need(0)) return true;
                    EmitAsm("LDA", Mem(_runtimeFdsResidentBankAddress));
                    return true;
__fds_farcall

Effectue un appel interbanque sans paramètre avec la forme propre au FDS. Le chargement et la restauration sont identiques à ceux de __fds_overlay_farcall().

Syntaxe

c
u8 __fds_farcall(u8 bank, u16 func);

Paramètres

bank

Expression représentant la banque. Elle est évaluée une fois avant l’appel, mais la banque réellement chargée est déterminée par l’emplacement de func après l’édition de liens. Cela n’appelle pas une adresse dans une banque arbitraire choisie à l’exécution. Passez une valeur correspondante, comme __bankof(func).

func

Nom d’une fonction déclarée sans paramètre. L’exemple passe u8 overlay_answer(void). Les adresses numériques, les pointeurs de fonction à l’exécution et les fonctions demandant des arguments sont refusés. Utilisez un appel ordinaire target(a, b) si des arguments sont nécessaires.

Valeur renvoyée

Résultat sur huit bits de la fonction cible. Si le chargement échoue mais que la banque précédente peut être restaurée, la cible n’est pas appelée et l’octet d’erreur de chargement est renvoyé à la place. Définissez une convention de résultat qui distingue une erreur d’un résultat normal.

Conditions et limites

Utilisez cette API avec --mapper=fds --fds-layout=fds32. La banque 0 contient le code commun à $A000–$DFFF. La banque 1 occupe $6000–$9FFF au démarrage ; les banques 2 et suivantes sont des overlays chargés dans cette même fenêtre de 16 Kio. Placez le code ou les données d’un overlay avec une directive telle que #pragma fixed_bank 2.

La banque 1 est restaurée depuis un fichier PRG de démarrage distinct de 16 384 octets à $6000. Sans manifeste, les fichiers par défaut ont l’ID 0 pour la banque 1, l’ID 1 pour le code commun et l’ID 2 pour CHR. Un manifeste personnalisé doit scinder le PRG de démarrage en fichiers de 16 384 octets à $6000 et $A000. Pour les banques à partir de 2, l’identifiant est --fds-overlay-start-id (32 par défaut) + numéro de banque − 2.

Lors d’un appel vers une autre banque, la banque précédente est mémorisée puis restaurée avant le retour. Les appels imbriqués suivent la même règle. Une restauration est également tentée après un transfert partiellement échoué. Si la restauration échoue elle-même, l’exécution s’arrête dans le code commun au lieu de revenir à du code écrasé. Les appels vers la banque commune ou la même banque évitent les transferts inutiles.

Les appels BIOS sont synchrones ; cette API ne fait pas avancer la boucle de jeu une fois par image pendant un chargement. Utilisez-la depuis le code de contrôle ordinaire de la banque commune, et non depuis un gestionnaire d’interruption. La continuité de l’affichage et de l’audio ainsi que la coexistence avec le véritable BIOS doivent être vérifiées séparément.

Ces captures utilisent un programme de test original de la convention d’appel LoadFiles pour vérifier le code CPU compilé, les transferts mémoire et chaque pixel affiché. Elles ne vérifient ni le véritable BIOS, ni la durée des transferts disque, ni le CRC, ni le démarrage sur matériel réel. Le modèle de contrôleur FDS de KUROSAKI est partiel ; une exécution ordinaire sans ce programme de test ne prouve pas les mêmes résultats de transfert.

La première commande de compilation ci-dessous construit l’exemple téléchargeable. Le vérificateur Python compile les cinq leçons avec et sans optimisation, puis les vérifie dans KUROSAKI. Il crée et supprime un programme de test temporaire à partir de scripts/fds_abi_fixture.py ; ces vérifications ne nécessitent aucun BIOS externe. Ce programme de test n’est pas un BIOS pour les jeux ordinaires.

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Avec le programme de test original de l’ABI LoadFiles, RETURN VALUE vaut 042 ; CALLBACK COUNT et BANK EXPR COUNT valent chacun 001 ; BANK IN CALLBACK vaut 002 et BANK AFTER RETURN vaut 001. La fonction et l’expression de banque ont été exécutées une fois chacune, et la banque 1 a été restaurée après l’appel à la banque 2. Ce n’est pas une capture d’un véritable test de transfert disque.

__fds_farcall: FARCALL-DEFAULT — Avec le programme de test original de l’ABI LoadFiles, RETURN VALUE vaut 042 ; CALLBACK COUNT et BANK EXPR COUNT valent chacun 001 ; BANK IN CALLBACK vaut 002 et BANK AFTER RETURN vaut 001. La fonction et l’expression de banque ont été exécutées une fois chacune, et la banque 1 a été restaurée après l’appel à la banque 2. Ce n’est pas une capture d’un véritable test de transfert disque.
FARCALL-DEFAULT — Avec le programme de test original de l’ABI LoadFiles, RETURN VALUE vaut 042 ; CALLBACK COUNT et BANK EXPR COUNT valent chacun 001 ; BANK IN CALLBACK vaut 002 et BANK AFTER RETURN vaut 001. La fonction et l’expression de banque ont été exécutées une fois chacune, et la banque 1 a été restaurée après l’appel à la banque 2. Ce n’est pas une capture d’un véritable test de transfert disque.

Programme d’exemple complet

Commande de compilation

powershell
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.py
Implémentation et source

kitaqfc/lib/fds_overlay.h:25

Notes de déclaration (texte source)
Same named, zero-argument dispatch as __fds_overlay_farcall; evaluate bank once.

kitaqfc/kitaqfc/CodeGenerator.cs:4074

csharp
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

Compare la banque demandée à la valeur de résidence mémorisée. Peut aider à décider si un chargement est nécessaire, mais ne l’effectue pas.

Syntaxe

c
u8 __fds_is_bank_resident(u8 bank);

Paramètres

bank

Numéro de banque logique à comparer. Le démarrage standard mémorise 1 ; les overlays générés utilisent 2 et plus. La banque 0 contient le code commun et n’est pas la valeur initiale de cet enregistrement de résidence.

Valeur renvoyée

1 si une banque valide correspond à la valeur mémorisée, sinon 0. La banque 255 (inconnue) renvoie toujours zéro, même lorsque la valeur mémorisée est 255.

Conditions et limites

Compilez avec --mapper=fds. Pour un mapper de cartouche, appeler une fonction réservée au FDS autre que __fds_available est refusé avec KQFC2420.

Le démarrage FDS standard initialise la banque mémorisée à 1. __fds_load_bank() la met à jour après un chargement réussi. Les chargements bruts via __fds_load_file() ou __fds_load_overlay(), ainsi que les transferts de banque échoués, fixent cette valeur à 255 (inconnue). Aucun contenu mémoire n’est vérifié ; ne vous fiez pas à cette information après modification directe de la fenêtre. Une routine de réinitialisation personnalisée doit gérer elle-même l’initialisation.

La capture utilise le chargement direct des fichiers de démarrage du disque en RAM par KUROSAKI. Cet exemple n’appelle aucune routine BIOS et ne vérifie ni les transferts de fichiers, ni le changement de disque, ni le démarrage sur matériel réel. Le micrologiciel de test vide n’est pas un BIOS de remplacement. L’exemple utilise la NMI pour l’affichage et désactive les IRQ masquables, inutiles à ces requêtes.

L’exemple de compilation produit un .fds à disposition native, sans les fichiers de démarrage supplémentaires de l’écran d’approbation. Cette configuration sert à vérifier le démarrage direct de KUROSAKI ; la procédure de démarrage d’une disquette sur matériel réel doit être validée séparément.

Exemple d’utilisation

c
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(); }
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

CURRENT BANK vaut 1, BANK 1 RESIDENT vaut 1, BANK 2 RESIDENT vaut 0 et OVERLAY FUNCTIONS vaut 2. Placer deux fonctions dans la banque 2 ne rend pas cette banque résidente au démarrage. Dans la compilation de vérification avec --fds-no-overlay-table, seule la dernière valeur devient zéro.

__fds_is_bank_resident: RESIDENCY-FDS-DEFAULT — CURRENT BANK vaut 1, BANK 1 RESIDENT vaut 1, BANK 2 RESIDENT vaut 0 et OVERLAY FUNCTIONS vaut 2. Placer deux fonctions dans la banque 2 ne rend pas cette banque résidente au démarrage. Dans la compilation de vérification avec --fds-no-overlay-table, seule la dernière valeur devient zéro.
RESIDENCY-FDS-DEFAULT — CURRENT BANK vaut 1, BANK 1 RESIDENT vaut 1, BANK 2 RESIDENT vaut 0 et OVERLAY FUNCTIONS vaut 2. Placer deux fonctions dans la banque 2 ne rend pas cette banque résidente au démarrage. Dans la compilation de vérification avec --fds-no-overlay-table, seule la dernière valeur devient zéro.
__fds_is_bank_resident: RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK vaut 1, BANK 1 RESIDENT vaut 1, BANK 2 RESIDENT vaut 0 et OVERLAY FUNCTIONS vaut 2. Placer deux fonctions dans la banque 2 ne rend pas cette banque résidente au démarrage. Dans la compilation de vérification avec --fds-no-overlay-table, seule la dernière valeur devient zéro.
RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK vaut 1, BANK 1 RESIDENT vaut 1, BANK 2 RESIDENT vaut 0 et OVERLAY FUNCTIONS vaut 2. Placer deux fonctions dans la banque 2 ne rend pas cette banque résidente au démarrage. Dans la compilation de vérification avec --fds-no-overlay-table, seule la dernière valeur devient zéro.

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/fds_overlay.h:18

Notes de déclaration (texte source)
Compare a valid bank with the software resident-bank byte; FF (unknown) never matches.

kitaqfc/kitaqfc/CodeGenerator.cs:9465

csharp
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

Convertit une banque logique en identifiant de fichier et charge la fenêtre d’overlay $6000–$9FFF, puis mémorise cette banque comme résidente.

Syntaxe

c
u8 __fds_load_bank(u8 bank);

Paramètres

bank

Banque logique à charger, de 1 à 254. La banque 0 est du code commun, pas une fenêtre chargeable ; 255 est réservé à une résidence inconnue. À partir de la banque 2, l’identifiant de fichier calculé doit aussi tenir dans 0..254.

Valeur renvoyée

Zéro en cas de succès. Renvoie 255 pour les banques 0 ou 255, un dépassement de l’identifiant de fichier ou une banque 1 sans fichier de restauration. Un transfert BIOS échoué renvoie son octet d’erreur. Une erreur d’argument avant le transfert conserve la banque mémorisée ; un échec de transfert BIOS la marque 255 (inconnue).

Conditions et limites

Utilisez cette API avec --mapper=fds --fds-layout=fds32. La banque 0 contient le code commun à $A000–$DFFF. La banque 1 occupe $6000–$9FFF au démarrage ; les banques 2 et suivantes sont des overlays chargés dans cette même fenêtre de 16 Kio. Placez le code ou les données d’un overlay avec une directive telle que #pragma fixed_bank 2.

La banque 1 est restaurée depuis un fichier PRG de démarrage distinct de 16 384 octets à $6000. Sans manifeste, les fichiers par défaut ont l’ID 0 pour la banque 1, l’ID 1 pour le code commun et l’ID 2 pour CHR. Un manifeste personnalisé doit scinder le PRG de démarrage en fichiers de 16 384 octets à $6000 et $A000. Pour les banques à partir de 2, l’identifiant est --fds-overlay-start-id (32 par défaut) + numéro de banque − 2.

Avec la vérification de résidence par défaut, une banque déjà enregistrée renvoie zéro sans lecture disque. --fds-no-overlay-guard désactive ce raccourci dans __fds_load_bank(). Un chargement réussi mémorise la banque demandée. Cette information ne valide pas le contenu de la mémoire.

Charger une autre banque directement depuis du code dans la fenêtre $6000–$9FFF peut écraser la fonction en cours ou son adresse de retour. Cet exemple place l’appelant et le code d’affichage dans #pragma fixed_bank 0. Pour appeler une fonction d’une autre banque puis revenir, utilisez __fds_overlay_farcall() ou un appel de fonction typé ordinaire.

Les appels BIOS sont synchrones ; cette API ne fait pas avancer la boucle de jeu une fois par image pendant un chargement. Utilisez-la depuis le code de contrôle ordinaire de la banque commune, et non depuis un gestionnaire d’interruption. La continuité de l’affichage et de l’audio ainsi que la coexistence avec le véritable BIOS doivent être vérifiées séparément.

Ces captures utilisent un programme de test original de la convention d’appel LoadFiles pour vérifier le code CPU compilé, les transferts mémoire et chaque pixel affiché. Elles ne vérifient ni le véritable BIOS, ni la durée des transferts disque, ni le CRC, ni le démarrage sur matériel réel. Le modèle de contrôleur FDS de KUROSAKI est partiel ; une exécution ordinaire sans ce programme de test ne prouve pas les mêmes résultats de transfert.

La première commande de compilation ci-dessous construit l’exemple téléchargeable. Le vérificateur Python compile les cinq leçons avec et sans optimisation, puis les vérifie dans KUROSAKI. Il crée et supprime un programme de test temporaire à partir de scripts/fds_abi_fixture.py ; ces vérifications ne nécessitent aucun BIOS externe. Ce programme de test n’est pas un BIOS pour les jeux ordinaires.

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Avec le programme de test original de l’ABI LoadFiles, LOAD BANK 2 STATUS, REQUIRE SAME BANK et RESTORE BANK 1 valent tous 000. FIRST / LAST BYTE affiche 011 et 044, issus des quatre octets transférés ; RESIDENT BANK vaut 001 après restauration. Les banques invalides 0 et 255 renvoient chacune 255. Demander la même banque ne provoque aucun chargement supplémentaire : les requêtes de fichiers vérifiées sont 32, puis 0.

__fds_load_bank: BANK_LOAD-DEFAULT — Avec le programme de test original de l’ABI LoadFiles, LOAD BANK 2 STATUS, REQUIRE SAME BANK et RESTORE BANK 1 valent tous 000. FIRST / LAST BYTE affiche 011 et 044, issus des quatre octets transférés ; RESIDENT BANK vaut 001 après restauration. Les banques invalides 0 et 255 renvoient chacune 255. Demander la même banque ne provoque aucun chargement supplémentaire : les requêtes de fichiers vérifiées sont 32, puis 0.
BANK_LOAD-DEFAULT — Avec le programme de test original de l’ABI LoadFiles, LOAD BANK 2 STATUS, REQUIRE SAME BANK et RESTORE BANK 1 valent tous 000. FIRST / LAST BYTE affiche 011 et 044, issus des quatre octets transférés ; RESIDENT BANK vaut 001 après restauration. Les banques invalides 0 et 255 renvoient chacune 255. Demander la même banque ne provoque aucun chargement supplémentaire : les requêtes de fichiers vérifiées sont 32, puis 0.

Programme d’exemple complet

Commande de compilation

powershell
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.py
Implémentation et source

kitaqfc/lib/fds_overlay.h:11

Notes de déclaration (texte source)
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

csharp
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

Appelle BIOS LoadFiles avec un identifiant de fichier disque plutôt qu’un numéro de banque logique. L’en-tête du fichier détermine la destination.

Syntaxe

c
u8 __fds_load_overlay(u8 id);

Paramètres

id

Identifiant de fichier disque, de 0 à 254. Ne passez pas 255 : il termine la liste de fichiers du BIOS. Dans la disposition par défaut, l’ID 32 contient la banque 2.

Valeur renvoyée

Octet d’erreur BIOS, zéro indiquant un succès. Après un succès comme après un échec, la résidence est marquée 255 (inconnue) ; un identifiant de fichier seul ne définit pas le mappage d’une banque logique.

Conditions et limites

Utilisez cette API avec --mapper=fds --fds-layout=fds32. La banque 0 contient le code commun à $A000–$DFFF. La banque 1 occupe $6000–$9FFF au démarrage ; les banques 2 et suivantes sont des overlays chargés dans cette même fenêtre de 16 Kio. Placez le code ou les données d’un overlay avec une directive telle que #pragma fixed_bank 2.

Construit une liste contenant l’ID demandé et le terminateur 255. Vérifiez la destination du manifeste pour éviter d’écraser le code commun ou en cours d’exécution. Préférez __fds_load_bank() pour les overlays logiques : cette fonction gère la résidence.

Charger une autre banque directement depuis du code dans la fenêtre $6000–$9FFF peut écraser la fonction en cours ou son adresse de retour. Cet exemple place l’appelant et le code d’affichage dans #pragma fixed_bank 0. Pour appeler une fonction d’une autre banque puis revenir, utilisez __fds_overlay_farcall() ou un appel de fonction typé ordinaire.

Les appels BIOS sont synchrones ; cette API ne fait pas avancer la boucle de jeu une fois par image pendant un chargement. Utilisez-la depuis le code de contrôle ordinaire de la banque commune, et non depuis un gestionnaire d’interruption. La continuité de l’affichage et de l’audio ainsi que la coexistence avec le véritable BIOS doivent être vérifiées séparément.

Ces captures utilisent un programme de test original de la convention d’appel LoadFiles pour vérifier le code CPU compilé, les transferts mémoire et chaque pixel affiché. Elles ne vérifient ni le véritable BIOS, ni la durée des transferts disque, ni le CRC, ni le démarrage sur matériel réel. Le modèle de contrôleur FDS de KUROSAKI est partiel ; une exécution ordinaire sans ce programme de test ne prouve pas les mêmes résultats de transfert.

La première commande de compilation ci-dessous construit l’exemple téléchargeable. Le vérificateur Python compile les cinq leçons avec et sans optimisation, puis les vérifie dans KUROSAKI. Il crée et supprime un programme de test temporaire à partir de scripts/fds_abi_fixture.py ; ces vérifications ne nécessitent aucun BIOS externe. Ce programme de test n’est pas un BIOS pour les jeux ordinaires.

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Avec le programme de test original de l’ABI LoadFiles, FILE 32 LOAD STATUS vaut 000 et les octets aux extrémités des données transférées valent 011 et 044. Après le chargement brut, UNKNOWN BANK RECORD vaut 255 et IS BANK 1 RESIDENT vaut 000. REQUIRE BANK 1 réussit avec 000, puis RESIDENT AFTER RELOAD vaut 001. Cela distingue le chargement par identifiant de fichier de la gestion d’une banque logique.

__fds_load_overlay: RAW_OVERLAY-DEFAULT — Avec le programme de test original de l’ABI LoadFiles, FILE 32 LOAD STATUS vaut 000 et les octets aux extrémités des données transférées valent 011 et 044. Après le chargement brut, UNKNOWN BANK RECORD vaut 255 et IS BANK 1 RESIDENT vaut 000. REQUIRE BANK 1 réussit avec 000, puis RESIDENT AFTER RELOAD vaut 001. Cela distingue le chargement par identifiant de fichier de la gestion d’une banque logique.
RAW_OVERLAY-DEFAULT — Avec le programme de test original de l’ABI LoadFiles, FILE 32 LOAD STATUS vaut 000 et les octets aux extrémités des données transférées valent 011 et 044. Après le chargement brut, UNKNOWN BANK RECORD vaut 255 et IS BANK 1 RESIDENT vaut 000. REQUIRE BANK 1 réussit avec 000, puis RESIDENT AFTER RELOAD vaut 001. Cela distingue le chargement par identifiant de fichier de la gestion d’une banque logique.

Programme d’exemple complet

Commande de compilation

powershell
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.py
Implémentation et source

kitaqfc/lib/fds_overlay.h:7

Notes de déclaration (texte source)
Load disk file id through BIOS, return its error byte and mark bank residency FF (unknown).

kitaqfc/kitaqfc/CodeGenerator.cs:9375

csharp
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

Appelle une fonction sans paramètre dans un overlay et restaure la banque précédente avant le retour.

Syntaxe

c
u8 __fds_overlay_farcall(u8 bank, u16 func);

Paramètres

bank

Expression représentant la banque. Elle est évaluée une fois avant l’appel, mais la banque réellement chargée est déterminée par l’emplacement de func après l’édition de liens. Cela n’appelle pas une adresse dans une banque arbitraire choisie à l’exécution. Passez une valeur correspondante, comme __bankof(func).

func

Nom d’une fonction déclarée sans paramètre. L’exemple passe u8 overlay_answer(void). Les adresses numériques, les pointeurs de fonction à l’exécution et les fonctions demandant des arguments sont refusés. Utilisez un appel ordinaire target(a, b) si des arguments sont nécessaires.

Valeur renvoyée

Résultat sur huit bits de la fonction cible. Si le chargement échoue mais que la banque précédente peut être restaurée, la cible n’est pas appelée et l’octet d’erreur de chargement est renvoyé à la place. Définissez une convention de résultat qui distingue une erreur d’un résultat normal.

Conditions et limites

Utilisez cette API avec --mapper=fds --fds-layout=fds32. La banque 0 contient le code commun à $A000–$DFFF. La banque 1 occupe $6000–$9FFF au démarrage ; les banques 2 et suivantes sont des overlays chargés dans cette même fenêtre de 16 Kio. Placez le code ou les données d’un overlay avec une directive telle que #pragma fixed_bank 2.

La banque 1 est restaurée depuis un fichier PRG de démarrage distinct de 16 384 octets à $6000. Sans manifeste, les fichiers par défaut ont l’ID 0 pour la banque 1, l’ID 1 pour le code commun et l’ID 2 pour CHR. Un manifeste personnalisé doit scinder le PRG de démarrage en fichiers de 16 384 octets à $6000 et $A000. Pour les banques à partir de 2, l’identifiant est --fds-overlay-start-id (32 par défaut) + numéro de banque − 2.

Lors d’un appel vers une autre banque, la banque précédente est mémorisée puis restaurée avant le retour. Les appels imbriqués suivent la même règle. Une restauration est également tentée après un transfert partiellement échoué. Si la restauration échoue elle-même, l’exécution s’arrête dans le code commun au lieu de revenir à du code écrasé. Les appels vers la banque commune ou la même banque évitent les transferts inutiles.

Les appels BIOS sont synchrones ; cette API ne fait pas avancer la boucle de jeu une fois par image pendant un chargement. Utilisez-la depuis le code de contrôle ordinaire de la banque commune, et non depuis un gestionnaire d’interruption. La continuité de l’affichage et de l’audio ainsi que la coexistence avec le véritable BIOS doivent être vérifiées séparément.

Ces captures utilisent un programme de test original de la convention d’appel LoadFiles pour vérifier le code CPU compilé, les transferts mémoire et chaque pixel affiché. Elles ne vérifient ni le véritable BIOS, ni la durée des transferts disque, ni le CRC, ni le démarrage sur matériel réel. Le modèle de contrôleur FDS de KUROSAKI est partiel ; une exécution ordinaire sans ce programme de test ne prouve pas les mêmes résultats de transfert.

La première commande de compilation ci-dessous construit l’exemple téléchargeable. Le vérificateur Python compile les cinq leçons avec et sans optimisation, puis les vérifie dans KUROSAKI. Il crée et supprime un programme de test temporaire à partir de scripts/fds_abi_fixture.py ; ces vérifications ne nécessitent aucun BIOS externe. Ce programme de test n’est pas un BIOS pour les jeux ordinaires.

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Avec le programme de test original de l’ABI LoadFiles, RETURN VALUE vaut 042 ; CALLBACK COUNT et BANK EXPR COUNT valent chacun 001 ; BANK IN CALLBACK vaut 002 et BANK AFTER RETURN vaut 001. La fonction et l’expression de banque ont été exécutées une fois chacune, et la banque 1 a été restaurée après l’appel à la banque 2. Ce n’est pas une capture d’un véritable test de transfert disque.

__fds_overlay_farcall: OVERLAY_CALL-DEFAULT — Avec le programme de test original de l’ABI LoadFiles, RETURN VALUE vaut 042 ; CALLBACK COUNT et BANK EXPR COUNT valent chacun 001 ; BANK IN CALLBACK vaut 002 et BANK AFTER RETURN vaut 001. La fonction et l’expression de banque ont été exécutées une fois chacune, et la banque 1 a été restaurée après l’appel à la banque 2. Ce n’est pas une capture d’un véritable test de transfert disque.
OVERLAY_CALL-DEFAULT — Avec le programme de test original de l’ABI LoadFiles, RETURN VALUE vaut 042 ; CALLBACK COUNT et BANK EXPR COUNT valent chacun 001 ; BANK IN CALLBACK vaut 002 et BANK AFTER RETURN vaut 001. La fonction et l’expression de banque ont été exécutées une fois chacune, et la banque 1 a été restaurée après l’appel à la banque 2. Ce n’est pas une capture d’un véritable test de transfert disque.

Programme d’exemple complet

Commande de compilation

powershell
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.py
Implémentation et source

kitaqfc/lib/fds_overlay.h:23

Notes de déclaration (texte source)
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

csharp
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

Renvoie le nombre de fonctions placées par l’éditeur de liens dans les banques d’overlay à partir de 2. Ce n’est ni un nombre de fonctions résidentes ni un nombre de fichiers.

Syntaxe

c
u8 __fds_overlay_function_count(void);

Valeur renvoyée

Octet de nombre placé en tête de table, de 0 à 255. Vaut zéro s’il n’y a aucune fonction d’overlay ou si --fds-no-overlay-table est utilisé. Plus de 255 entrées sont refusées avec KQFC2511.

Conditions et limites

Compilez avec --mapper=fds. Pour un mapper de cartouche, appeler une fonction réservée au FDS autre que __fds_available est refusé avec KQFC2420.

La génération de code crée un emplacement provisoire nul ; l’édition de liens FDS native le remplace par la table après placement des banques et adresses. L’exemple place deux fonctions dans la banque 2 sans les appeler. Les données initiales de --fds-layout=legacy ne constituent pas un décompte des fonctions d’overlay.

La capture utilise le chargement direct des fichiers de démarrage du disque en RAM par KUROSAKI. Cet exemple n’appelle aucune routine BIOS et ne vérifie ni les transferts de fichiers, ni le changement de disque, ni le démarrage sur matériel réel. Le micrologiciel de test vide n’est pas un BIOS de remplacement. L’exemple utilise la NMI pour l’affichage et désactive les IRQ masquables, inutiles à ces requêtes.

L’exemple de compilation produit un .fds à disposition native, sans les fichiers de démarrage supplémentaires de l’écran d’approbation. Cette configuration sert à vérifier le démarrage direct de KUROSAKI ; la procédure de démarrage d’une disquette sur matériel réel doit être validée séparément.

Exemple d’utilisation

c
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(); }
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

CURRENT BANK vaut 1, BANK 1 RESIDENT vaut 1, BANK 2 RESIDENT vaut 0 et OVERLAY FUNCTIONS vaut 2. Placer deux fonctions dans la banque 2 ne rend pas cette banque résidente au démarrage. Dans la compilation de vérification avec --fds-no-overlay-table, seule la dernière valeur devient zéro.

__fds_overlay_function_count: RESIDENCY-FDS-DEFAULT — CURRENT BANK vaut 1, BANK 1 RESIDENT vaut 1, BANK 2 RESIDENT vaut 0 et OVERLAY FUNCTIONS vaut 2. Placer deux fonctions dans la banque 2 ne rend pas cette banque résidente au démarrage. Dans la compilation de vérification avec --fds-no-overlay-table, seule la dernière valeur devient zéro.
RESIDENCY-FDS-DEFAULT — CURRENT BANK vaut 1, BANK 1 RESIDENT vaut 1, BANK 2 RESIDENT vaut 0 et OVERLAY FUNCTIONS vaut 2. Placer deux fonctions dans la banque 2 ne rend pas cette banque résidente au démarrage. Dans la compilation de vérification avec --fds-no-overlay-table, seule la dernière valeur devient zéro.
__fds_overlay_function_count: RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK vaut 1, BANK 1 RESIDENT vaut 1, BANK 2 RESIDENT vaut 0 et OVERLAY FUNCTIONS vaut 2. Placer deux fonctions dans la banque 2 ne rend pas cette banque résidente au démarrage. Dans la compilation de vérification avec --fds-no-overlay-table, seule la dernière valeur devient zéro.
RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK vaut 1, BANK 1 RESIDENT vaut 1, BANK 2 RESIDENT vaut 0 et OVERLAY FUNCTIONS vaut 2. Placer deux fonctions dans la banque 2 ne rend pas cette banque résidente au démarrage. Dans la compilation de vérification avec --fds-no-overlay-table, seule la dernière valeur devient zéro.

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/fds_overlay.h:20

Notes de déclaration (texte source)
Return the linker-generated overlay-function count; zero if the table is disabled.

kitaqfc/kitaqfc/CodeGenerator.cs:5229

csharp
case "__fds_overlay_function_count":
                    if (!Need(0)) return true;
                    EmitAsm("LDA", Abs("__kq_fds_overlay_function_table"));
                    return true;
__fds_require_bank

Charge une banque uniquement si elle n’est pas déjà mémorisée comme résidente. Appelez cette fonction avant d’accéder à une ressource pour éviter un transfert inutile si elle est déjà disponible.

Syntaxe

c
u8 __fds_require_bank(u8 bank);

Paramètres

bank

Banque logique à charger, de 1 à 254. La banque 0 est du code commun, pas une fenêtre chargeable ; 255 est réservé à une résidence inconnue. À partir de la banque 2, l’identifiant de fichier calculé doit aussi tenir dans 0..254.

Valeur renvoyée

Zéro en cas de succès. Renvoie 255 pour les banques 0 ou 255, un dépassement de l’identifiant de fichier ou une banque 1 sans fichier de restauration. Un transfert BIOS échoué renvoie son octet d’erreur. Une erreur d’argument avant le transfert conserve la banque mémorisée ; un échec de transfert BIOS la marque 255 (inconnue).

Conditions et limites

Utilisez cette API avec --mapper=fds --fds-layout=fds32. La banque 0 contient le code commun à $A000–$DFFF. La banque 1 occupe $6000–$9FFF au démarrage ; les banques 2 et suivantes sont des overlays chargés dans cette même fenêtre de 16 Kio. Placez le code ou les données d’un overlay avec une directive telle que #pragma fixed_bank 2.

Une banque valide correspondant à la valeur mémorisée renvoie zéro ; sinon, cette fonction appelle __fds_load_bank(). La comparaison initiale reste active avec --fds-no-overlay-guard. La banque 255 n’est jamais considérée comme résidente, même si la valeur mémorisée est 255.

La banque 1 est restaurée depuis un fichier PRG de démarrage distinct de 16 384 octets à $6000. Sans manifeste, les fichiers par défaut ont l’ID 0 pour la banque 1, l’ID 1 pour le code commun et l’ID 2 pour CHR. Un manifeste personnalisé doit scinder le PRG de démarrage en fichiers de 16 384 octets à $6000 et $A000. Pour les banques à partir de 2, l’identifiant est --fds-overlay-start-id (32 par défaut) + numéro de banque − 2.

Charger une autre banque directement depuis du code dans la fenêtre $6000–$9FFF peut écraser la fonction en cours ou son adresse de retour. Cet exemple place l’appelant et le code d’affichage dans #pragma fixed_bank 0. Pour appeler une fonction d’une autre banque puis revenir, utilisez __fds_overlay_farcall() ou un appel de fonction typé ordinaire.

Les appels BIOS sont synchrones ; cette API ne fait pas avancer la boucle de jeu une fois par image pendant un chargement. Utilisez-la depuis le code de contrôle ordinaire de la banque commune, et non depuis un gestionnaire d’interruption. La continuité de l’affichage et de l’audio ainsi que la coexistence avec le véritable BIOS doivent être vérifiées séparément.

Ces captures utilisent un programme de test original de la convention d’appel LoadFiles pour vérifier le code CPU compilé, les transferts mémoire et chaque pixel affiché. Elles ne vérifient ni le véritable BIOS, ni la durée des transferts disque, ni le CRC, ni le démarrage sur matériel réel. Le modèle de contrôleur FDS de KUROSAKI est partiel ; une exécution ordinaire sans ce programme de test ne prouve pas les mêmes résultats de transfert.

La première commande de compilation ci-dessous construit l’exemple téléchargeable. Le vérificateur Python compile les cinq leçons avec et sans optimisation, puis les vérifie dans KUROSAKI. Il crée et supprime un programme de test temporaire à partir de scripts/fds_abi_fixture.py ; ces vérifications ne nécessitent aucun BIOS externe. Ce programme de test n’est pas un BIOS pour les jeux ordinaires.

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Avec le programme de test original de l’ABI LoadFiles, LOAD BANK 2 STATUS, REQUIRE SAME BANK et RESTORE BANK 1 valent tous 000. FIRST / LAST BYTE affiche 011 et 044, issus des quatre octets transférés ; RESIDENT BANK vaut 001 après restauration. Les banques invalides 0 et 255 renvoient chacune 255. Demander la même banque ne provoque aucun chargement supplémentaire : les requêtes de fichiers vérifiées sont 32, puis 0.

__fds_require_bank: BANK_LOAD-DEFAULT — Avec le programme de test original de l’ABI LoadFiles, LOAD BANK 2 STATUS, REQUIRE SAME BANK et RESTORE BANK 1 valent tous 000. FIRST / LAST BYTE affiche 011 et 044, issus des quatre octets transférés ; RESIDENT BANK vaut 001 après restauration. Les banques invalides 0 et 255 renvoient chacune 255. Demander la même banque ne provoque aucun chargement supplémentaire : les requêtes de fichiers vérifiées sont 32, puis 0.
BANK_LOAD-DEFAULT — Avec le programme de test original de l’ABI LoadFiles, LOAD BANK 2 STATUS, REQUIRE SAME BANK et RESTORE BANK 1 valent tous 000. FIRST / LAST BYTE affiche 011 et 044, issus des quatre octets transférés ; RESIDENT BANK vaut 001 après restauration. Les banques invalides 0 et 255 renvoient chacune 255. Demander la même banque ne provoque aucun chargement supplémentaire : les requêtes de fichiers vérifiées sont 32, puis 0.

Programme d’exemple complet

Commande de compilation

powershell
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.py
Implémentation et source

kitaqfc/lib/fds_overlay.h:13

Notes de déclaration (texte source)
Return zero when the software residency record matches, otherwise call __fds_load_bank.

kitaqfc/kitaqfc/CodeGenerator.cs:9451

csharp
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 — Audio FDS à table d'ondes

Déclare les intrinsèques du compilateur pour l’onde de 64 pas, la table de modulation, la hauteur, le volume et les enveloppes. KITAQFC génère leurs implémentations.

Construisez une image .fds avec --mapper=fds. Cette leçon utilise le son sans entrées/sorties disque. L’enregistrement passe par le démarrage direct en RAM et le modèle audio de KUROSAKI. Le micrologiciel de test vide n’implémente aucune routine BIOS et n’est pas distribué. Le démarrage disque normal et le matériel réel nécessitent un environnement d’amorçage adapté.

__fds_env_set

Écrit les trois arguments bruts dans $4080, $4084 puis $408A.

Syntaxe

c
void __fds_env_set(u8 speed, u8 gain, u8 mode);

Paramètres

speed

Malgré le nom du paramètre, il s’agit de la valeur brute de $4080 : bit 7 volume direct, bit 6 enveloppe croissante.

gain

Valeur brute d’enveloppe de modulation écrite dans $4084.

mode

Vitesse maîtresse d’enveloppe écrite dans $408A.

Valeur renvoyée

Aucune valeur de retour. Modifie les registres audio ou l’état du pilote.

Conditions et limites

Construisez une image .fds avec --mapper=fds. Cette leçon utilise le son sans entrées/sorties disque. L’enregistrement passe par le démarrage direct en RAM et le modèle audio de KUROSAKI. Le micrologiciel de test vide n’implémente aucune routine BIOS et n’est pas distribué. Le démarrage disque normal et le matériel réel nécessitent un environnement d’amorçage adapté.

Exemple d’utilisation

c
// 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();
}

Résultat attendu

Un triangle original de 64 pas joue à environ 440 Hz, s’interrompt, joue à environ 660 Hz puis s’arrête. La seconde note utilise nes_fds_wave_load et des réglages bruts d’enveloppe. Cela vérifie le modèle audio et le démarrage direct, pas l’amorçage disque par BIOS.

__fds_env_set: NTSC
NTSC
NTSC — Audio enregistré

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/fds_sound.h:18

Notes de déclaration (texte source)
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

csharp
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

Écrit la fréquence de 12 bits dans $4082/$4083 et efface les bits de contrôle supérieurs de $4083.

Syntaxe

c
void __fds_freq_set(u16 freq);

Paramètres

freq

Valeur de registre de fréquence FDS de 0 à 4095. Les bits de contrôle supérieurs sont effacés.

Valeur renvoyée

Aucune valeur de retour. Modifie les registres audio ou l’état du pilote.

Conditions et limites

Construisez une image .fds avec --mapper=fds. Cette leçon utilise le son sans entrées/sorties disque. L’enregistrement passe par le démarrage direct en RAM et le modèle audio de KUROSAKI. Le micrologiciel de test vide n’implémente aucune routine BIOS et n’est pas distribué. Le démarrage disque normal et le matériel réel nécessitent un environnement d’amorçage adapté.

Exemple d’utilisation

c
// 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();
}

Résultat attendu

Un triangle original de 64 pas joue à environ 440 Hz, s’interrompt, joue à environ 660 Hz puis s’arrête. La seconde note utilise nes_fds_wave_load et des réglages bruts d’enveloppe. Cela vérifie le modèle audio et le démarrage direct, pas l’amorçage disque par BIOS.

__fds_freq_set: NTSC
NTSC
NTSC — Audio enregistré

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/fds_sound.h:13

Notes de déclaration (texte source)
Write a 12-bit frequency to 4082/4083; upper control bits in 4083 are cleared.

kitaqfc/kitaqfc/CodeGenerator.cs:5197

csharp
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

Envoie 32 octets dans l’ordre au port d’écriture de table de modulation $4088.

Syntaxe

c
void __fds_mod_load(const u8* mod32);

Paramètres

mod32

Trente-deux octets pour la table de modulation FDS, chacun un code de contrôle de 0 à 7. Arrêtez la modulation avant le chargement.

Valeur renvoyée

Aucune valeur de retour. Modifie les registres audio ou l’état du pilote.

Conditions et limites

Construisez une image .fds avec --mapper=fds. Cette leçon utilise le son sans entrées/sorties disque. L’enregistrement passe par le démarrage direct en RAM et le modèle audio de KUROSAKI. Le micrologiciel de test vide n’implémente aucune routine BIOS et n’est pas distribué. Le démarrage disque normal et le matériel réel nécessitent un environnement d’amorçage adapté.

Exemple d’utilisation

c
// 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();
}

Résultat attendu

Un triangle original de 64 pas joue à environ 440 Hz, s’interrompt, joue à environ 660 Hz puis s’arrête. La seconde note utilise nes_fds_wave_load et des réglages bruts d’enveloppe. Cela vérifie le modèle audio et le démarrage direct, pas l’amorçage disque par BIOS.

__fds_mod_load: NTSC
NTSC
NTSC — Audio enregistré

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/fds_sound.h:11

Notes de déclaration (texte source)
Stream 32 readable bytes to 4088; the caller prepares modulation state and write conditions.

kitaqfc/kitaqfc/CodeGenerator.cs:5195

csharp
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

Écrit 0x83 dans la commande disque/son $4023 et met à zéro le contrôle d’onde $4089 et la vitesse $408A.

Syntaxe

c
void __fds_sound_enable(void);

Valeur renvoyée

Aucune valeur de retour. Modifie les registres audio ou l’état du pilote.

Conditions et limites

Construisez une image .fds avec --mapper=fds. Cette leçon utilise le son sans entrées/sorties disque. L’enregistrement passe par le démarrage direct en RAM et le modèle audio de KUROSAKI. Le micrologiciel de test vide n’implémente aucune routine BIOS et n’est pas distribué. Le démarrage disque normal et le matériel réel nécessitent un environnement d’amorçage adapté.

Exemple d’utilisation

c
// 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();
}

Résultat attendu

Un triangle original de 64 pas joue à environ 440 Hz, s’interrompt, joue à environ 660 Hz puis s’arrête. La seconde note utilise nes_fds_wave_load et des réglages bruts d’enveloppe. Cela vérifie le modèle audio et le démarrage direct, pas l’amorçage disque par BIOS.

__fds_sound_enable: NTSC
NTSC
NTSC — Audio enregistré

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/fds_sound.h:7

Notes de déclaration (texte source)
Write 83 to 4023 and zero to 4089/408A to initialize disk/sound register gates.

kitaqfc/kitaqfc/CodeGenerator.cs:5183

csharp
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

Écrit les six bits bas du volume avec 0x80 dans $4080 pour le contrôle direct du volume.

Syntaxe

c
void __fds_volume_set(u8 vol);

Paramètres

vol

Volume direct FDS de 0 à 63 ; le masquage sur six bits ramène 64 à zéro.

Valeur renvoyée

Aucune valeur de retour. Modifie les registres audio ou l’état du pilote.

Conditions et limites

Construisez une image .fds avec --mapper=fds. Cette leçon utilise le son sans entrées/sorties disque. L’enregistrement passe par le démarrage direct en RAM et le modèle audio de KUROSAKI. Le micrologiciel de test vide n’implémente aucune routine BIOS et n’est pas distribué. Le démarrage disque normal et le matériel réel nécessitent un environnement d’amorçage adapté.

Exemple d’utilisation

c
// 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();
}

Résultat attendu

Un triangle original de 64 pas joue à environ 440 Hz, s’interrompt, joue à environ 660 Hz puis s’arrête. La seconde note utilise nes_fds_wave_load et des réglages bruts d’enveloppe. Cela vérifie le modèle audio et le démarrage direct, pas l’amorçage disque par BIOS.

__fds_volume_set: NTSC
NTSC
NTSC — Audio enregistré

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/fds_sound.h:15

Notes de déclaration (texte source)
Write (volume & 3F) | 80 to 4080 for direct volume control; masking wraps, rather than clamps.

kitaqfc/kitaqfc/CodeGenerator.cs:5200

csharp
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

Active l’écriture d’onde, copie 64 octets dans $4040..$407F, puis remet $4089 à zéro.

Syntaxe

c
void __fds_wave_load(const u8* wave64);

Paramètres

wave64

Soixante-quatre octets d’onde FDS, chacun représentant une amplitude sur six bits de 0 à 63. NULL n’est pas vérifié.

Valeur renvoyée

Aucune valeur de retour. Modifie les registres audio ou l’état du pilote.

Conditions et limites

Construisez une image .fds avec --mapper=fds. Cette leçon utilise le son sans entrées/sorties disque. L’enregistrement passe par le démarrage direct en RAM et le modèle audio de KUROSAKI. Le micrologiciel de test vide n’implémente aucune routine BIOS et n’est pas distribué. Le démarrage disque normal et le matériel réel nécessitent un environnement d’amorçage adapté.

Exemple d’utilisation

c
// 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();
}

Résultat attendu

Un triangle original de 64 pas joue à environ 440 Hz, s’interrompt, joue à environ 660 Hz puis s’arrête. La seconde note utilise nes_fds_wave_load et des réglages bruts d’enveloppe. Cela vérifie le modèle audio et le démarrage direct, pas l’amorçage disque par BIOS.

__fds_wave_load: NTSC
NTSC
NTSC — Audio enregistré

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/fds_sound.h:9

Notes de déclaration (texte source)
Copy exactly 64 readable bytes to wave RAM with wave writes enabled, then set 4089 to zero.

kitaqfc/kitaqfc/CodeGenerator.cs:5193

csharp
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 — Fonctions intrinsèques du compilateur

__nes_audio_vblank_tick

Point d’entrée NMI qui fait avancer les durées BGM et SFX et met à jour l’APU selon le contrôle actuel de chaque canal.

Syntaxe

c
void __nes_audio_vblank_tick(void);

Valeur renvoyée

Aucune valeur de retour.

Conditions et limites

Incluez audio_vblank.h et ajoutez audio_vblank.c à la compilation exactement une fois. Le compilateur appelle son point d’entrée une fois depuis le gestionnaire NMI, qu’il soit standard ou personnalisé. Activez la NMI du PPU. La boucle principale n’appelle pas de routine de mise à jour musicale.

Ce pilote à quatre canaux est réglé pour le NTSC : CH1=impulsion 1, CH2=impulsion 2, CH3=triangle et CH4=bruit, avec des indices de 0 à 3. Il ne gère ni le DMC ni les extensions audio. Ne l’utilisez pas en parallèle avec du code écrivant directement dans l’APU, comme les effets de audio.c. Il ne fournit ni table de fréquences PAL ni correction de tempo pour le PAL.

N’appelez pas cette fonction directement depuis l’application. L’édition de liens avec la bibliothèque musicale la raccorde à la NMI par l’intermédiaire du compilateur. Le point d’entrée sauvegarde A/X/Y et n’utilise pas la zone temporaire partagée des expressions. Ce point d’entrée et les tables de fréquences résident en ROM fixe ; aucune banque ROM n’est commutée dans la NMI. Cette préservation concerne uniquement le point d’entrée musical. Un gestionnaire NMI personnalisé reste responsable de la sauvegarde de l’état utilisé par ses autres traitements.

Un enregistrement contient cinq octets : delay, CH1, CH2, CH3, CH4. Une durée de 1 à 255 indique le nombre de ticks NMI pendant lesquels le nouvel état reste actif. Zéro termine la lecture et les quatre autres octets sont ignorés. Les notes de 0 à 71 correspondent à C2–B7 (24=C4). Pour le bruit, les valeurs 0 à 15 sélectionnent une période en mode long, et 16 à 31 le mode court. Sur tous les canaux, 255=NES_AUDIO_HOLD conserve l’état et 254=NES_AUDIO_STOP coupe le son.

Exemple d’utilisation

c
// 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();

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple joue une fois une phrase originale à quatre canaux. La musique continue pendant que le programme principal effectue des calculs entiers durant 90 images sans remplir la file. « PHRASE COMPLETE / SILENT » et « UNDERRUNS 0 / MATH OK » indiquent que la lecture est terminée, qu’aucun manque de données n’a eu lieu et que les calculs ont donné les résultats attendus. Écoutez l’enregistrement pour reconnaître la mélodie et l’accompagnement à impulsions, la basse triangulaire et le bruit. La fin est silencieuse. L’image présente les résultats du contrôle ; l’audio permet d’évaluer le son.

__nes_audio_vblank_tick: NROM
NROM
NROM — Audio enregistré

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/audio_vblank.h:70

Notes de déclaration (texte source)
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

c
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

Écrit l’octet complet d’attributs compactés à 0x23C0 + (y>>2)*8 + (x>>2) dans la table 0. X/Y sont des coordonnées de tuiles désignant une zone d’attributs de 4×4 tuiles. Remplace les quatre champs de palette sur deux bits, sans fusionner uniquement un quadrant choisi.

Syntaxe

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

Paramètres

x / y

Coordonnées de tuiles, pas de pixels. Gardez x dans 0..31 et y dans 0..29 pour la carte visible de 32×30 tuiles ; chaque tuile couvre 8×8 pixels. Les arguments d’un octet ne sont pas bornés automatiquement et peuvent écrire hors de la zone voulue.

attr

Octet d’attributs compactés : bits 0–1 pour les 2×2 tuiles en haut à gauche, 2–3 en haut à droite, 4–5 en bas à gauche, 6–7 en bas à droite. Chaque champ choisit une palette de 0 à 3. 0x55, 0xAA et 0xFF choisissent la palette 1, 2 ou 3 dans les quatre quadrants.

Valeur renvoyée

Aucune (void).

Conditions et limites

L’accès direct à la PPU exige de désactiver le rendu ou de terminer toute l’opération dans un intervalle VBlank sûr. Ces fonctions n’attendent pas, ne mettent rien en file, ne bloquent pas NMI et ne rétablissent pas le défilement. Empêchez une interruption de modifier le verrou ou l’adresse partagés pendant l’appel. Le bit 2 de PPUCTRL choisit un pas de destination de 1 ou 32 par octet. La PPU gère les miroirs et le bouclage des adresses ; la CHR-ROM n’est pas inscriptible. Rétablissez le défilement voulu avant le rendu.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
for(i=2;i<12;i=(u8)(i+4)) {
__attr_set(i,6,0xFF);__attr_set(i,10,0xFF); // All quadrants select green.
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__attr_set: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:134

Notes de déclaration (texte source)
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

csharp
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

Sélectionne la table logique nt & 3, réinitialise le verrou PPU et écrit un octet complet d’attributs à 0x23C0 + (nt & 3)*0x400 + (y>>2)*8 + (x>>2). Écrase tous les quadrants de palette de cet octet. La sélection suit le mirroring actuel de la cartouche.

Syntaxe

c
void __attr_set_nt(u8 nt, u8 x, u8 y, u8 attr);

Paramètres

nt

Sélecteur de table logique. Seuls les deux bits faibles sont utilisés (nt & 3) : 4 choisit la table 0 et 7 la table 3. N’alloue pas de mémoire indépendante et ne change pas le mirroring du mapper.

x / y

Coordonnées de tuiles, pas de pixels. Gardez x dans 0..31 et y dans 0..29 pour la carte visible de 32×30 tuiles ; chaque tuile couvre 8×8 pixels. Les arguments d’un octet ne sont pas bornés automatiquement et peuvent écrire hors de la zone voulue.

attr

Octet d’attributs compactés : bits 0–1 pour les 2×2 tuiles en haut à gauche, 2–3 en haut à droite, 4–5 en bas à gauche, 6–7 en bas à droite. Chaque champ choisit une palette de 0 à 3. 0x55, 0xAA et 0xFF choisissent la palette 1, 2 ou 3 dans les quatre quadrants.

Valeur renvoyée

Aucune (void).

Conditions et limites

L’accès direct à la PPU exige de désactiver le rendu ou de terminer toute l’opération dans un intervalle VBlank sûr. Ces fonctions n’attendent pas, ne mettent rien en file, ne bloquent pas NMI et ne rétablissent pas le défilement. Empêchez une interruption de modifier le verrou ou l’adresse partagés pendant l’appel. Le bit 2 de PPUCTRL choisit un pas de destination de 1 ou 32 par octet. La PPU gère les miroirs et le bouclage des adresses ; la CHR-ROM n’est pas inscriptible. Rétablissez le défilement voulu avant le rendu.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
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.
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__attr_set_nt: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:136

Notes de déclaration (texte source)
Write a whole packed attribute byte in nametable nt&3; other quadrant fields are overwritten.

kitaqfc/kitaqfc/CodeGenerator.cs:4825

csharp
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

Obtient le numéro de banque ROM logique attribué à un objet de données ou à une fonction nommée. Résout une information de placement, sans consulter la banque active ni déréférencer un pointeur.

Syntaxe

c
u8 bank = __bankof(symbol);

Paramètres

symbol

Nom d’un objet ROM ou d’une fonction dont le placement est connu du compilateur ou de l’éditeur de liens. Une variable pointeur ne désigne pas ici l’objet pointé ; une expression d’exécution n’est pas un nom de symbole.

Valeur renvoyée

Un numéro de banque u8. Zéro peut désigner la ROM commune, sans être un sélecteur valide sur toutes les cibles.

Exemple d’utilisation

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

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple vérifie que payload et callback sont tous deux placés en banque 3. Cela valide les métadonnées de placement ; les lectures et contrôles du rappel vérifient séparément le comportement à l’exécution.

Le programme complet montre comment identifier des données par une banque et une adresse proche, appeler des rappels entre banques et distinguer l’état de la bibliothèque du mappage réel. Tous les nombres sont hexadécimaux. BANK IN et BANK OUT doivent valoir 02 ; BYTE HEX, D3 ; WORD HEX, 5C7A ; FAILED CHECKS et FIRST FAILURE, 00. Le texte est bleu sur CGB et FC, noir sur DMG.

CALL COUNT vaut 02 : la macro far_call et __farcall invoquent chacun le rappel une fois.

__bankof: MMC3
MMC3

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:77

Notes de déclaration (texte source)
PRG bank
Resolve a symbol or function placement to its byte bank number at compilation time.

kitaqfc/kitaqfc/CodeGenerator.cs:6906

csharp
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

Sélectionne une banque PRG logique avec la routine du compilateur propre au mapper et actualise son état interne. Le changement persiste, sans modifier le registre distinct de la bibliothèque bank.

Syntaxe

c
void __bankswitch(u8 bank);

Paramètres

bank

Numéro de banque ROM non signé sur 8 bits. Pour un appel, il doit correspondre au placement de la fonction ; pour une lecture, il sélectionne la source. Respectez la plage du type de cartouche.

Valeur renvoyée

Aucune (void).

Conditions et limites

Un changement persistant de banque ne doit pas rendre inaccessibles le code exécuté ou son adresse de retour. Placez cet appel, bank_switch elle-même si elle est utilisée, et le code qui suit dans la banque commune ou fixe 0, comme dans l’exemple.

L’état de la bibliothèque commence à zéro et ne détecte pas le mappage initial de la cartouche. Initialisez-le avec bank_switch. Les intrinsèques et écritures directes dans le mapper n’actualisent pas cet état distinct ; bank_get_current ne vérifie donc pas le mappage matériel réel.

Les banques FC sont des numéros logiques du compilateur. Zéro désigne la ROM commune, pas une cible de commutation ; la routine le ramène à la banque 1. SUROM512 accepte 1–30 : une constante hors plage transmise directement à l’intrinsèque de commutation provoque une erreur de compilation ; une valeur d’exécution invalide sélectionne 1 et active l’état de défaut du mapper. Lisez directement la ROM commune, sans demander la banque zéro.

Exemple d’utilisation

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

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

bank_switch(2) enregistre et sélectionne d’abord la banque 2. Une commutation directe sélectionne ensuite la banque 1 et lit A1 dans home1, tandis que bank_get_current indique toujours 2 ; après restauration, home2 donne à nouveau B2.

Le programme complet montre comment identifier des données par une banque et une adresse proche, appeler des rappels entre banques et distinguer l’état de la bibliothèque du mappage réel. Tous les nombres sont hexadécimaux. BANK IN et BANK OUT doivent valoir 02 ; BYTE HEX, D3 ; WORD HEX, 5C7A ; FAILED CHECKS et FIRST FAILURE, 00. Le texte est bleu sur CGB et FC, noir sur DMG.

CALL COUNT vaut 02 : la macro far_call et __farcall invoquent chacun le rappel une fois.

__bankswitch: MMC3
MMC3

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:79

Notes de déclaration (texte source)
Select the mapper-specific PRG bank; the common bank and legal bank range depend on the target board.

kitaqfc/kitaqfc/CodeGenerator.cs:4746

csharp
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

Efface le drapeau choisi à 0 sans effacer le reste de l’octet. Utilisez-le pour retirer une condition enregistrée, par exemple un état temporaire. Effacer de nouveau le même bit le laisse à 0.

Syntaxe

c
void __bit_clear(u8* base, u16 bit);

Paramètres

base

Pointeur vers le premier octet du tableau de drapeaux en mémoire accessible au CPU. Cet appel n’alloue ni n’initialise le tableau. Utilisez de la RAM accessible en écriture pour mettre, effacer ou inverser les bits ; le test ne fait que lire.

bit

Indice de bit u16 à partir de zéro (0–65535), et non décalage d’octet ou masque. L’indice 0 choisit le masque 0x01 dans l’octet 0 ; 7 choisit 0x80 dans l’octet 0 ; 8 choisit 0x01 dans l’octet 1. En général, le décalage est index / 8 et le masque 1 << (index % 8). Pour un tableau de N octets, exigez index < 8*N et une plage d’adresses CPU valide.

Valeur renvoyée

Aucune (void).

Conditions et limites

L’appelant doit fournir assez de stockage. Il n’y a ni argument de longueur, ni découpage aux limites, ni changement de banque, ni transfert graphique automatique. Un grand numéro de bit peut viser un octet bien au-delà du tableau. Gardez le mappage mémoire stable ; n’utilisez pas ces fonctions de drapeaux RAM comme raccourci pour des registres matériels dont les accès ont des effets de bord.

Mise à 1, effacement et inversion lisent un octet, modifient le bit choisi puis réécrivent l’octet. Cette séquence n’est pas atomique. Coordonnez les écritures de la boucle principale et des gestionnaires d’interruption pour ne pas perdre une modification intermédiaire d’un autre bit du même octet.

Le code FC peut intégrer un indice constant ou appeler une fonction auxiliaire générée. Les deux formes utilisent la même numérotation des bits et le même masque de résultat. Ces fonctions partagent les arguments et l’espace temporaire ; n’y réentrez pas depuis NMI/IRQ pendant un autre appel auxiliaire.

Exemple d’utilisation

c
__bit_clear(bit_flags,2);
bit_clear_value=bit_flags[0]; // A6 & FB = A2, decimal 162.
__bit_clear(bit_flags,2);     // Clearing it twice leaves it clear.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme complet arrive ici avec l’octet 0 à 0xA6. Effacer le bit 2 donne 0xA2 (binaire 10100010, décimal 162) ; CLEAR BIT2 doit afficher 162. Un nouvel effacement du bit 2 ne change rien. Les autres drapeaux sont préservés dans les deux appels.

Valeurs FC attendues en décimal : TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE vient de l’activation de l’indice 9 dans le second octet, de 0x80 à 0x82, sans modifier l’octet 0. La capture FC utilise du texte bleu sur noir.

Le programme vérifie chaque transition affichée et indique le nombre d’échecs. Mettre, effacer et inverser ne renvoient rien ; les lignes affichent l’octet RAM obtenu. Des tests distincts sur émulateur comparent les 520 octets de tampons protégés dans 136 cas par plateforme : indices constants et calculés franchissant les limites d’octet et de 256 octets, et les huit masques non nuls possibles. Ce sont des vérifications d’état RAM, pas une démonstration de dessin. Le matériel physique et les écritures concurrentes par interruption n’ont pas été testés.

__bit_clear: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:45

Notes de déclaration (texte source)
Clear the indexed bit in caller storage; no bounds check or interrupt exclusion is inserted.

kitaqfc/kitaqfc/CodeGenerator.cs:4640

csharp
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

Met le drapeau choisi à 1 en préservant les sept autres bits du même octet. Vous pouvez ainsi marquer une quête terminée ou un objet ramassé. Remettre à 1 un bit déjà à 1 ne le change pas ; ce n’est pas une inversion.

Syntaxe

c
void __bit_set(u8* base, u16 bit);

Paramètres

base

Pointeur vers le premier octet du tableau de drapeaux en mémoire accessible au CPU. Cet appel n’alloue ni n’initialise le tableau. Utilisez de la RAM accessible en écriture pour mettre, effacer ou inverser les bits ; le test ne fait que lire.

bit

Indice de bit u16 à partir de zéro (0–65535), et non décalage d’octet ou masque. L’indice 0 choisit le masque 0x01 dans l’octet 0 ; 7 choisit 0x80 dans l’octet 0 ; 8 choisit 0x01 dans l’octet 1. En général, le décalage est index / 8 et le masque 1 << (index % 8). Pour un tableau de N octets, exigez index < 8*N et une plage d’adresses CPU valide.

Valeur renvoyée

Aucune (void).

Conditions et limites

L’appelant doit fournir assez de stockage. Il n’y a ni argument de longueur, ni découpage aux limites, ni changement de banque, ni transfert graphique automatique. Un grand numéro de bit peut viser un octet bien au-delà du tableau. Gardez le mappage mémoire stable ; n’utilisez pas ces fonctions de drapeaux RAM comme raccourci pour des registres matériels dont les accès ont des effets de bord.

Mise à 1, effacement et inversion lisent un octet, modifient le bit choisi puis réécrivent l’octet. Cette séquence n’est pas atomique. Coordonnez les écritures de la boucle principale et des gestionnaires d’interruption pour ne pas perdre une modification intermédiaire d’un autre bit du même octet.

Le code FC peut intégrer un indice constant ou appeler une fonction auxiliaire générée. Les deux formes utilisent la même numérotation des bits et le même masque de résultat. Ces fonctions partagent les arguments et l’espace temporaire ; n’y réentrez pas depuis NMI/IRQ pendant un autre appel auxiliaire.

Exemple d’utilisation

c
__bit_set(bit_flags,1);
bit_set_value=bit_flags[0]; // A4 | 02 = A6, decimal 166.
__bit_set(bit_flags,1);     // Setting it twice leaves it set.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Mettez à 1 le bit 1 de 0xA4 : 10100100 | 00000010 devient 10100110, soit 0xA6 (166 en décimal). La ligne SET BIT1 doit afficher 166. L’exemple répète l’opération et vérifie que l’octet reste à 0xA6, montrant que des mises à 1 répétées n’inversent pas le drapeau.

Valeurs FC attendues en décimal : TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE vient de l’activation de l’indice 9 dans le second octet, de 0x80 à 0x82, sans modifier l’octet 0. La capture FC utilise du texte bleu sur noir.

Le programme vérifie chaque transition affichée et indique le nombre d’échecs. Mettre, effacer et inverser ne renvoient rien ; les lignes affichent l’octet RAM obtenu. Des tests distincts sur émulateur comparent les 520 octets de tampons protégés dans 136 cas par plateforme : indices constants et calculés franchissant les limites d’octet et de 256 octets, et les huit masques non nuls possibles. Ce sont des vérifications d’état RAM, pas une démonstration de dessin. Le matériel physique et les écritures concurrentes par interruption n’ont pas été testés.

__bit_set: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:43

Notes de déclaration (texte source)
Set the indexed bit in caller storage without changing the other seven bits in that byte.

kitaqfc/kitaqfc/CodeGenerator.cs:4637

csharp
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

Lit un drapeau dans un tableau de bits compactés sans modifier le tableau. Huit drapeaux indépendants peuvent ainsi partager chaque octet de RAM. Le résultat indique si le bit choisi est à zéro ou à un ; sa représentation numérique dépend de la cible.

Syntaxe

c
u8 __bit_test(u8* base, u16 bit);

Paramètres

base

Pointeur vers le premier octet du tableau de drapeaux en mémoire accessible au CPU. Cet appel n’alloue ni n’initialise le tableau. Utilisez de la RAM accessible en écriture pour mettre, effacer ou inverser les bits ; le test ne fait que lire.

bit

Indice de bit u16 à partir de zéro (0–65535), et non décalage d’octet ou masque. L’indice 0 choisit le masque 0x01 dans l’octet 0 ; 7 choisit 0x80 dans l’octet 0 ; 8 choisit 0x01 dans l’octet 1. En général, le décalage est index / 8 et le masque 1 << (index % 8). Pour un tableau de N octets, exigez index < 8*N et une plage d’adresses CPU valide.

Valeur renvoyée

u8 : 0 si le bit est effacé, sinon son masque : 1, 2, 4, 8, 16, 32, 64 ou 128. Le résultat est base[bit >> 3] & (1 << (bit & 7)). Tester le bit 7 actif renvoie par exemple 128. Utilisez result != 0 pour savoir si le drapeau est actif ; une comparaison à 1 manquerait les autres positions de bit.

Conditions et limites

L’appelant doit fournir assez de stockage. Il n’y a ni argument de longueur, ni découpage aux limites, ni changement de banque, ni transfert graphique automatique. Un grand numéro de bit peut viser un octet bien au-delà du tableau. Gardez le mappage mémoire stable ; n’utilisez pas ces fonctions de drapeaux RAM comme raccourci pour des registres matériels dont les accès ont des effets de bord.

Le code FC peut intégrer un indice constant ou appeler une fonction auxiliaire générée. Les deux formes utilisent la même numérotation des bits et le même masque de résultat. Ces fonctions partagent les arguments et l’espace temporaire ; n’y réentrez pas depuis NMI/IRQ pendant un autre appel auxiliaire.

Exemple d’utilisation

c
bit_off=__bit_test(bit_flags,1); // Bit 1 of A4 is clear: zero.
bit_on=__bit_test(bit_flags,7);  // Bit 7 is set: GB returns 1; FC returns 128.
bit_is_on=0;
if(bit_on!=0)bit_is_on=1; // The same nonzero test works on both targets.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Commencez avec l’octet 0 à 0xA4 (binaire 10100100). Tester le bit 1 renvoie 0 ; tester le bit 7 renvoie 1 sur GB ou 128 sur FC. L’exemple convertit aussi ce résultat non nul en drapeau commun et vérifie que l’octet initial est inchangé. Regardez TEST OFF et TEST ON dans l’écran ci-dessous.

Valeurs FC attendues en décimal : TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE vient de l’activation de l’indice 9 dans le second octet, de 0x80 à 0x82, sans modifier l’octet 0. La capture FC utilise du texte bleu sur noir.

Le programme vérifie chaque transition affichée et indique le nombre d’échecs. Mettre, effacer et inverser ne renvoient rien ; les lignes affichent l’octet RAM obtenu. Des tests distincts sur émulateur comparent les 520 octets de tampons protégés dans 136 cas par plateforme : indices constants et calculés franchissant les limites d’octet et de 256 octets, et les huit masques non nuls possibles. Ce sont des vérifications d’état RAM, pas une démonstration de dessin. Le matériel physique et les écritures concurrentes par interruption n’ont pas été testés.

__bit_test: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:41

Notes de déclaration (texte source)
Return base[bit>>3] & (1<<(bit&7)): zero or the bit mask, not a normalized Boolean.

kitaqfc/kitaqfc/CodeGenerator.cs:4634

csharp
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

Inverse le drapeau choisi : 0 devient 1 et 1 devient 0. Les autres bits sont préservés. Deux appels sur le même bit rétablissent son état initial, à condition qu’aucune autre écriture de ce bit ne survienne entre les appels.

Syntaxe

c
void __bit_toggle(u8* base, u16 bit);

Paramètres

base

Pointeur vers le premier octet du tableau de drapeaux en mémoire accessible au CPU. Cet appel n’alloue ni n’initialise le tableau. Utilisez de la RAM accessible en écriture pour mettre, effacer ou inverser les bits ; le test ne fait que lire.

bit

Indice de bit u16 à partir de zéro (0–65535), et non décalage d’octet ou masque. L’indice 0 choisit le masque 0x01 dans l’octet 0 ; 7 choisit 0x80 dans l’octet 0 ; 8 choisit 0x01 dans l’octet 1. En général, le décalage est index / 8 et le masque 1 << (index % 8). Pour un tableau de N octets, exigez index < 8*N et une plage d’adresses CPU valide.

Valeur renvoyée

Aucune (void).

Conditions et limites

L’appelant doit fournir assez de stockage. Il n’y a ni argument de longueur, ni découpage aux limites, ni changement de banque, ni transfert graphique automatique. Un grand numéro de bit peut viser un octet bien au-delà du tableau. Gardez le mappage mémoire stable ; n’utilisez pas ces fonctions de drapeaux RAM comme raccourci pour des registres matériels dont les accès ont des effets de bord.

Mise à 1, effacement et inversion lisent un octet, modifient le bit choisi puis réécrivent l’octet. Cette séquence n’est pas atomique. Coordonnez les écritures de la boucle principale et des gestionnaires d’interruption pour ne pas perdre une modification intermédiaire d’un autre bit du même octet.

Le code FC peut intégrer un indice constant ou appeler une fonction auxiliaire générée. Les deux formes utilisent la même numérotation des bits et le même masque de résultat. Ces fonctions partagent les arguments et l’espace temporaire ; n’y réentrez pas depuis NMI/IRQ pendant un autre appel auxiliaire.

Exemple d’utilisation

c
__bit_toggle(bit_flags,7);
bit_toggle_value=bit_flags[0]; // A2 ^ 80 = 22, decimal 34.
__bit_toggle(bit_flags,7);
bit_restore_value=bit_flags[0]; // Two toggles restore A2.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme complet commence cette étape à 0xA2. Inverser le bit 7 donne 0x22 (34 en décimal), donc TOGGLE BIT7 affiche 034. L’inverser encore restaure 0xA2 et RESTORE affiche 162. Ces observations montrent les deux sens de l’inversion tout en préservant les autres bits.

Valeurs FC attendues en décimal : TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE vient de l’activation de l’indice 9 dans le second octet, de 0x80 à 0x82, sans modifier l’octet 0. La capture FC utilise du texte bleu sur noir.

Le programme vérifie chaque transition affichée et indique le nombre d’échecs. Mettre, effacer et inverser ne renvoient rien ; les lignes affichent l’octet RAM obtenu. Des tests distincts sur émulateur comparent les 520 octets de tampons protégés dans 136 cas par plateforme : indices constants et calculés franchissant les limites d’octet et de 256 octets, et les huit masques non nuls possibles. Ce sont des vérifications d’état RAM, pas une démonstration de dessin. Le matériel physique et les écritures concurrentes par interruption n’ont pas été testés.

__bit_toggle: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:47

Notes de déclaration (texte source)
XOR the indexed bit in caller storage; coordinate concurrent writers to avoid lost updates.

kitaqfc/kitaqfc/CodeGenerator.cs:4643

csharp
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

Sélectionne une banque CHR. Sur MMC3, sélectionne R0 et modifie la fenêtre de 2 Kio à PPU $0000–$07FF. Sur CNROM, modifie toute la région de 8 Kio à $0000–$1FFF.

Syntaxe

c
void __chr_bank_set(u8 bank);

Paramètres

bank

Sur CNROM, c’est un numéro de banque CHR de 8 Kio. Sur MMC3, c’est un numéro de banque de 1 Kio sélectionnant une fenêtre de 2 Kio : le bit de poids faible est ignoré, donc 2 et 3 sélectionnent tous deux la paire de banques de 1 Kio 2 et 3. Les banques disponibles dépendent de la capacité de la CHR ROM ; l’API ne contrôle pas les limites.

Valeur renvoyée

Aucune valeur de retour.

Conditions et limites

Les déclarations des intrinsèques sont dans intrinsics.h. Incluez nes_game.h pour les macros nes_mapper_irq_*. Choisissez la carte à la compilation avec --mapper.

Écrire 0 ou 1 dans le registre de sélection MMC3 efface aussi le bit 6 de mode PRG et le bit 7 d’inversion CHR. N’utilisez pas cette opération avec une disposition PRG/CHR qui exige de les maintenir actifs. Une commutation pendant le rendu modifie les lectures de motifs suivantes. L’exemple vérifie avec le rendu désactivé et restaure les banques de police avant d’afficher les résultats.

Ces trois intrinsèques prennent en charge CNROM et MMC3. Les autres cartes sont refusées à la compilation. SUROM utilise aussi le contrôle CHR pour sélectionner la région PRG externe et n’autorise pas ces opérations CHR.

Exemple d’utilisation

c
__chr_bank_set(2);
    mapper_result[1] = mapper_read(0x03FF);
    mapper_result[2] = mapper_read(0x07FF);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple MMC3 affiche les marqueurs CHR 19/20, 21/22 et 23/24. Les lectures CIRAM donnent A B A B en mirroring vertical et A A B B en horizontal. Les comptes IRQ sont 01→02→03→03 : chacune des trois premières activations explicites autorise une interruption ; le compteur reste fixe après désactivation. Chaque étape attend six images.

__chr_bank_set: MMC3-DEFAULT
MMC3-DEFAULT

Programme d’exemple complet

Commande de compilation

powershell
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-disasm

Exemple d’utilisation

c
__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();

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

La leçon CNROM affiche les marqueurs 33, 25 et 41 après SET 2, SET0 1 et SET1 3. Elle lit des octets marqueurs originaux via le PPU pour montrer que les trois noms commutent toute la région de motifs de 8 Kio.

__chr_bank_set: CNROM-DEFAULT
CNROM-DEFAULT

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:316

Notes de déclaration (texte source)
Select a CNROM bank or MMC3 register 0 bank; unsupported mapper profiles produce a compile error.
__chr_bank_set0

Sélectionne MMC3 R0 et modifie la fenêtre de 2 Kio à PPU $0000–$07FF. Équivaut à __chr_bank_set. Sur CNROM, modifie toute la région de 8 Kio.

Syntaxe

c
void __chr_bank_set0(u8 bank);

Paramètres

bank

Sur CNROM, c’est un numéro de banque CHR de 8 Kio. Sur MMC3, c’est un numéro de banque de 1 Kio sélectionnant une fenêtre de 2 Kio : le bit de poids faible est ignoré, donc 2 et 3 sélectionnent tous deux la paire de banques de 1 Kio 2 et 3. Les banques disponibles dépendent de la capacité de la CHR ROM ; l’API ne contrôle pas les limites.

Valeur renvoyée

Aucune valeur de retour.

Conditions et limites

Les déclarations des intrinsèques sont dans intrinsics.h. Incluez nes_game.h pour les macros nes_mapper_irq_*. Choisissez la carte à la compilation avec --mapper.

Écrire 0 ou 1 dans le registre de sélection MMC3 efface aussi le bit 6 de mode PRG et le bit 7 d’inversion CHR. N’utilisez pas cette opération avec une disposition PRG/CHR qui exige de les maintenir actifs. Une commutation pendant le rendu modifie les lectures de motifs suivantes. L’exemple vérifie avec le rendu désactivé et restaure les banques de police avant d’afficher les résultats.

Ces trois intrinsèques prennent en charge CNROM et MMC3. Les autres cartes sont refusées à la compilation. SUROM utilise aussi le contrôle CHR pour sélectionner la région PRG externe et n’autorise pas ces opérations CHR.

Exemple d’utilisation

c
__chr_bank_set0(4);
    mapper_result[3] = mapper_read(0x03FF);
    mapper_result[4] = mapper_read(0x07FF);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple MMC3 affiche les marqueurs CHR 19/20, 21/22 et 23/24. Les lectures CIRAM donnent A B A B en mirroring vertical et A A B B en horizontal. Les comptes IRQ sont 01→02→03→03 : chacune des trois premières activations explicites autorise une interruption ; le compteur reste fixe après désactivation. Chaque étape attend six images.

__chr_bank_set0: MMC3-DEFAULT
MMC3-DEFAULT

Programme d’exemple complet

Commande de compilation

powershell
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-disasm

Exemple d’utilisation

c
__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();

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

La leçon CNROM affiche les marqueurs 33, 25 et 41 après SET 2, SET0 1 et SET1 3. Elle lit des octets marqueurs originaux via le PPU pour montrer que les trois noms commutent toute la région de motifs de 8 Kio.

__chr_bank_set0: CNROM-DEFAULT
CNROM-DEFAULT

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:318

Notes de déclaration (texte source)
Use the same CNROM/MMC3 register 0 selection as __chr_bank_set.
__chr_bank_set1

Sélectionne MMC3 R1 et modifie la fenêtre de 2 Kio à PPU $0800–$0FFF sans toucher R0. CNROM n’a pas de seconde fenêtre indépendante ; toute la région de 8 Kio est modifiée.

Syntaxe

c
void __chr_bank_set1(u8 bank);

Paramètres

bank

Sur CNROM, c’est un numéro de banque CHR de 8 Kio. Sur MMC3, c’est un numéro de banque de 1 Kio sélectionnant une fenêtre de 2 Kio : le bit de poids faible est ignoré, donc 2 et 3 sélectionnent tous deux la paire de banques de 1 Kio 2 et 3. Les banques disponibles dépendent de la capacité de la CHR ROM ; l’API ne contrôle pas les limites.

Valeur renvoyée

Aucune valeur de retour.

Conditions et limites

Les déclarations des intrinsèques sont dans intrinsics.h. Incluez nes_game.h pour les macros nes_mapper_irq_*. Choisissez la carte à la compilation avec --mapper.

Écrire 0 ou 1 dans le registre de sélection MMC3 efface aussi le bit 6 de mode PRG et le bit 7 d’inversion CHR. N’utilisez pas cette opération avec une disposition PRG/CHR qui exige de les maintenir actifs. Une commutation pendant le rendu modifie les lectures de motifs suivantes. L’exemple vérifie avec le rendu désactivé et restaure les banques de police avant d’afficher les résultats.

Ces trois intrinsèques prennent en charge CNROM et MMC3. Les autres cartes sont refusées à la compilation. SUROM utilise aussi le contrôle CHR pour sélectionner la région PRG externe et n’autorise pas ces opérations CHR.

Exemple d’utilisation

c
__chr_bank_set1(6);
    mapper_result[5] = mapper_read(0x0BFF);
    mapper_result[6] = mapper_read(0x0FFF);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple MMC3 affiche les marqueurs CHR 19/20, 21/22 et 23/24. Les lectures CIRAM donnent A B A B en mirroring vertical et A A B B en horizontal. Les comptes IRQ sont 01→02→03→03 : chacune des trois premières activations explicites autorise une interruption ; le compteur reste fixe après désactivation. Chaque étape attend six images.

__chr_bank_set1: MMC3-DEFAULT
MMC3-DEFAULT

Programme d’exemple complet

Commande de compilation

powershell
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-disasm

Exemple d’utilisation

c
__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();

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

La leçon CNROM affiche les marqueurs 33, 25 et 41 après SET 2, SET0 1 et SET1 3. Elle lit des octets marqueurs originaux via le PPU pour montrer que les trois noms commutent toute la région de motifs de 8 Kio.

__chr_bank_set1: CNROM-DEFAULT
CNROM-DEFAULT

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:320

Notes de déclaration (texte source)
Select MMC3 register 1, or the same full CNROM bank register; this is mapper-specific banking.
__copy16

Copie exactement 16 octets de src vers dst, sans argument de longueur. On peut ainsi copier un enregistrement de 16 octets ou une tuile GB/NES placée en mémoire accessible au processeur. Le nombre 16 désigne des octets, pas des bits ; cet appel ne charge pas les graphismes dans le matériel vidéo.

Syntaxe

c
void __copy16(u8* dst, const u8* src);

Paramètres

dst

Pointeur vers le premier octet accessible en écriture de la destination. Prévoyez une zone contiguë assez grande pour toute l’opération ; aucun tampon n’est alloué automatiquement.

src

Pointeur vers le premier octet lisible de la source. Toute la zone doit rester accessible et inchangée pendant la copie.

Valeur renvoyée

Aucune (void).

Conditions et limites

Utilisez des zones source et destination sans chevauchement. Ces routines copient vers les adresses croissantes et n’offrent pas la sécurité de memmove en cas de recouvrement ; écraser une partie de la source encore non lue peut altérer la suite de la copie.

L’opération se termine avant de rendre la main. Elle ne change pas de banque ROM/RAM et ne limite pas automatiquement la zone aux bornes d’un tampon. Maintenez le mappage stable, utilisez des zones valides et coordonnez l’accès avec les interruptions utilisant les mêmes tampons.

Sur FC, dst est une adresse CPU, pas une adresse PPU. Une copie en RAM CPU ne modifie ni CHR, ni les tables de noms, ni les palettes ; leur chargement nécessite les API PPU/VRAM. Les routines auxiliaires générées partagent les arguments et les temporaires : ne les rappelez pas depuis une NMI/IRQ pendant une opération en cours.

Exemple d’utilisation

c
memory_reset();
__copy16(memory_destination+1,memory_source+1);
memory_copy_16=memory_check(16,0); // Exactly 16 bytes, not 16 bits.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Cet exemple utilise __copy16 pour copier 16 octets suivant un motif connu à partir du décalage 1. memory_check compare les tableaux source et destination en entier ; la ligne COPY16 doit afficher 016. Le premier octet sert de garde et les octets suivant le bloc doivent également rester inchangés.

Le programme complet initialise deux tampons de 264 octets avant chaque opération, puis vérifie tous les octets de la source, de la destination et des zones de garde. L’écran attendu affiche COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 et FAILED 000. WORD/BYTE désignent la largeur du compteur, pas la taille des éléments copiés. Ces nombres proviennent des vérifications de l’exemple ; ces fonctions void ne renvoient aucune valeur.

Les captures montrent du texte noir sur blanc sur DMG, bleu sur blanc sur CGB et bleu sur noir sur FC. L’exemple vérifie des opérations en RAM ; le texte en présente les résultats, sans illustrer une primitive de dessin. Des contrôles distincts sur émulateur couvrent 88 cas par plateforme : longueurs constantes ou déterminées à l’exécution de 0, 1, 16, 17, 32, 33, 64, 65 et 255 octets, plus 256 et 257 pour les compteurs sur 16 bits. Aucun test sur matériel réel n’a été effectué.

__copy16: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:27

Notes de déclaration (texte source)
Copy exactly 16 bytes forward; both ranges must be valid and suitably non-overlapping.

kitaqfc/kitaqfc/CodeGenerator.cs:4620

csharp
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

Copie exactement 32 octets de src vers dst, sans argument de longueur. Utilisez cet appel pour un enregistrement fixe de 32 octets ou deux motifs de tuile contigus de 16 octets en mémoire accessible au processeur. La copie avance octet par octet ; ce n’est ni une lecture unique sur 32 bits ni un transfert avec changement de banque.

Syntaxe

c
void __copy32(u8* dst, const u8* src);

Paramètres

dst

Pointeur vers le premier octet accessible en écriture de la destination. Prévoyez une zone contiguë assez grande pour toute l’opération ; aucun tampon n’est alloué automatiquement.

src

Pointeur vers le premier octet lisible de la source. Toute la zone doit rester accessible et inchangée pendant la copie.

Valeur renvoyée

Aucune (void).

Conditions et limites

Utilisez des zones source et destination sans chevauchement. Ces routines copient vers les adresses croissantes et n’offrent pas la sécurité de memmove en cas de recouvrement ; écraser une partie de la source encore non lue peut altérer la suite de la copie.

L’opération se termine avant de rendre la main. Elle ne change pas de banque ROM/RAM et ne limite pas automatiquement la zone aux bornes d’un tampon. Maintenez le mappage stable, utilisez des zones valides et coordonnez l’accès avec les interruptions utilisant les mêmes tampons.

Sur FC, dst est une adresse CPU, pas une adresse PPU. Une copie en RAM CPU ne modifie ni CHR, ni les tables de noms, ni les palettes ; leur chargement nécessite les API PPU/VRAM. Les routines auxiliaires générées partagent les arguments et les temporaires : ne les rappelez pas depuis une NMI/IRQ pendant une opération en cours.

Exemple d’utilisation

c
memory_reset();
__copy32(memory_destination+1,memory_source+1);
memory_copy_32=memory_check(32,0);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Cet exemple utilise __copy32 pour copier 32 octets suivant un motif connu à partir du décalage 1. memory_check compare les tableaux source et destination en entier ; la ligne COPY32 doit afficher 032. Le premier octet sert de garde et les octets suivant le bloc doivent également rester inchangés.

Le programme complet initialise deux tampons de 264 octets avant chaque opération, puis vérifie tous les octets de la source, de la destination et des zones de garde. L’écran attendu affiche COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 et FAILED 000. WORD/BYTE désignent la largeur du compteur, pas la taille des éléments copiés. Ces nombres proviennent des vérifications de l’exemple ; ces fonctions void ne renvoient aucune valeur.

Les captures montrent du texte noir sur blanc sur DMG, bleu sur blanc sur CGB et bleu sur noir sur FC. L’exemple vérifie des opérations en RAM ; le texte en présente les résultats, sans illustrer une primitive de dessin. Des contrôles distincts sur émulateur couvrent 88 cas par plateforme : longueurs constantes ou déterminées à l’exécution de 0, 1, 16, 17, 32, 33, 64, 65 et 255 octets, plus 256 et 257 pour les compteurs sur 16 bits. Aucun test sur matériel réel n’a été effectué.

__copy32: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:29

Notes de déclaration (texte source)
Copy exactly 32 bytes forward; bank selection and source lifetime belong to the caller.

kitaqfc/kitaqfc/CodeGenerator.cs:4623

csharp
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

Calcule un produit scalaire non signé à deux termes.

Syntaxe

c
u16 __dot2_q8_8(u16 a0, u16 a1, u8 b0, u8 b1);

Paramètres

a0

Mot non signé de 0 à 65535.

a1

Mot non signé de 0 à 65535.

b0

Coefficient non signé de 0 à 255.

b1

Coefficient non signé de 0 à 255.

Valeur renvoyée

Renvoie les 16 bits de poids faible de a0*b0+a1*b1.

Conditions et limites

Sur FC, les fonctions nommées q8_8/q1_7 utilisent les produits entiers bruts, sans décalage de mise à l’échelle fixe. Les fonctions Q1.7 sont des alias des opérations signées correspondantes. L’appelant gère les unités ; les résultats signés sont renvoyés comme motifs de bits u16.

Exemple d’utilisation

c
// 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);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Comparez les entrées littérales aux valeurs en RAM. Les résultats représentatifs sont affichés sur 16 bits non signés ; les négatifs utilisent le complément à deux. Le GB remet à l’échelle les produits à virgule fixe, tandis que le FC renvoie des produits entiers : des entrées identiques ne donnent donc pas nécessairement les mêmes résultats.

L’extrait stocke les valeurs suivantes sur 16 bits : result[22]=64959, result[23]=64959. Chaque calcul avec une entrée littérale correspond à son équivalent avec une entrée en RAM. L’écran affiche des valeurs représentatives ; le tableau result conserve tous les résultats.

__dot2_q8_8: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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.chr
Implémentation et source

kitaqfc/lib/intrinsics.h:63

Notes de déclaration (texte source)
Return a0*b0+a1*b1 modulo 65536. The FC backend inserts no Q8.8 rescaling or rounding.

kitaqfc/kitaqfc/CodeGenerator.cs:4654

csharp
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

Calcule un produit scalaire non signé à trois termes.

Syntaxe

c
u16 __dot3_q8_8(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);

Paramètres

a0

Mot non signé de 0 à 65535.

a1

Mot non signé de 0 à 65535.

a2

Mot non signé de 0 à 65535.

b0

Coefficient non signé de 0 à 255.

b1

Coefficient non signé de 0 à 255.

b2

Coefficient non signé de 0 à 255.

Valeur renvoyée

Renvoie les 16 bits de poids faible de a0*b0+a1*b1+a2*b2.

Conditions et limites

Sur FC, les fonctions nommées q8_8/q1_7 utilisent les produits entiers bruts, sans décalage de mise à l’échelle fixe. Les fonctions Q1.7 sont des alias des opérations signées correspondantes. L’appelant gère les unités ; les résultats signés sont renvoyés comme motifs de bits u16.

Exemple d’utilisation

c
// 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);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Comparez les entrées littérales aux valeurs en RAM. Les résultats représentatifs sont affichés sur 16 bits non signés ; les négatifs utilisent le complément à deux. Le GB remet à l’échelle les produits à virgule fixe, tandis que le FC renvoie des produits entiers : des entrées identiques ne donnent donc pas nécessairement les mêmes résultats.

L’extrait stocke les valeurs suivantes sur 16 bits : result[24]=2498, result[25]=2498. Chaque calcul avec une entrée littérale correspond à son équivalent avec une entrée en RAM. L’écran affiche des valeurs représentatives ; le tableau result conserve tous les résultats.

__dot3_q8_8: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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.chr
Implémentation et source

kitaqfc/lib/intrinsics.h:65

Notes de déclaration (texte source)
Sum three unsigned word-by-byte products modulo 65536 without a final scaling shift.

kitaqfc/kitaqfc/CodeGenerator.cs:4655

csharp
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 appelle la même routine du compilateur que __pad_read1_d1. Il lit D1 à $4016 avec la même séquence de verrouillage, le même résultat de huit bits et les mêmes effets de bord. Ce nom indique l’usage pour l’extension ; il n’active aucun autre mode de périphérique et ne combine pas D0 et D1.

Syntaxe

c
u8 __exp_pad_read1(void);

Valeur renvoyée

Masque de maintien de 8 bits dans l’ordre NES. Chaque bit à 1 identifie un bouton appuyé ; 0 signifie aucun. Ce n’est pas un booléen normalisé.

Conditions et limites

Format NES brut : A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit à 1 indique un appui. Utilisez ces valeurs avec nes_pad_* et les résultats bruts de __pad_*, et non les masques communs BTN_* de input.h.

Utilisez un périphérique fournissant un flux série de manette compatible sur huit bits par la ligne D1 choisie. La fonction n’identifie pas le périphérique et ne prend pas en charge tous les protocoles d’extension. Chaque appel verrouille à nouveau les deux flux de contrôleurs ; terminez une lecture complète avant d’en commencer une autre. Les résultats sont de nouvelles lectures qui ne mettent pas à jour les états input_* ou nes_pad1_*.

Le modèle de contrôleurs du KUROSAKI publié ne renvoie que D0 et le bit 6 fixe. D1 et le bit du microphone valent donc toujours 0 dans cet émulateur, même lorsqu’un bouton ordinaire est maintenu. L’exécution fournie vérifie la compilation, l’entrée normale et cette observation de signal nul. Les signaux périphériques non nuls, les délais électriques et la compatibilité matérielle restent non vérifiés ; un zéro ici ne prouve ni le bon fonctionnement du périphérique ni l’impossibilité de le lire sur une machine réelle.

Exemple d’utilisation

c
expansion_first=__exp_pad_read1(); // Same helper as __pad_read1_d1.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Ce programme compare l’entrée ordinaire D0, l’extension D1 et le niveau numérique du microphone. Maintenez A sur la manette 1 et B sur la manette 2 jusqu’à la capture. NORMAL P1 et NORMAL P2 affichent 001 et 002. Dans l’exécution KUROSAKI fournie, D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL et MIC DIRECT affichent 000, car ces lignes ne sont pas émulées. Les valeurs restent ensuite fixes. Avec des périphériques adaptés et leurs lignes implémentées, les valeurs dépendent des entrées réelles ; interprétez-les selon les bits décrits ci-dessus au lieu d’attendre ces zéros.

Le modèle de contrôleurs du KUROSAKI publié ne renvoie que D0 et le bit 6 fixe. D1 et le bit du microphone valent donc toujours 0 dans cet émulateur, même lorsqu’un bouton ordinaire est maintenu. L’exécution fournie vérifie la compilation, l’entrée normale et cette observation de signal nul. Les signaux périphériques non nuls, les délais électriques et la compatibilité matérielle restent non vérifiés ; un zéro ici ne prouve ni le bon fonctionnement du périphérique ni l’impossibilité de le lire sur une machine réelle.

__exp_pad_read1: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:227

Notes de déclaration (texte source)
Alias the port-1 D1 controller reader; the attached device must use the expected serial protocol.
__exp_pad_read2

__exp_pad_read2 appelle la même routine du compilateur que __pad_read2_d1. Il lit D1 à $4017 avec la même séquence de verrouillage, le même résultat de huit bits et les mêmes effets de bord. Ce nom indique l’usage pour l’extension ; il n’active aucun autre mode de périphérique et ne combine pas D0 et D1.

Syntaxe

c
u8 __exp_pad_read2(void);

Valeur renvoyée

Masque de maintien de 8 bits dans l’ordre NES. Chaque bit à 1 identifie un bouton appuyé ; 0 signifie aucun. Ce n’est pas un booléen normalisé.

Conditions et limites

Format NES brut : A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit à 1 indique un appui. Utilisez ces valeurs avec nes_pad_* et les résultats bruts de __pad_*, et non les masques communs BTN_* de input.h.

Utilisez un périphérique fournissant un flux série de manette compatible sur huit bits par la ligne D1 choisie. La fonction n’identifie pas le périphérique et ne prend pas en charge tous les protocoles d’extension. Chaque appel verrouille à nouveau les deux flux de contrôleurs ; terminez une lecture complète avant d’en commencer une autre. Les résultats sont de nouvelles lectures qui ne mettent pas à jour les états input_* ou nes_pad1_*.

Le modèle de contrôleurs du KUROSAKI publié ne renvoie que D0 et le bit 6 fixe. D1 et le bit du microphone valent donc toujours 0 dans cet émulateur, même lorsqu’un bouton ordinaire est maintenu. L’exécution fournie vérifie la compilation, l’entrée normale et cette observation de signal nul. Les signaux périphériques non nuls, les délais électriques et la compatibilité matérielle restent non vérifiés ; un zéro ici ne prouve ni le bon fonctionnement du périphérique ni l’impossibilité de le lire sur une machine réelle.

Exemple d’utilisation

c
expansion_second=__exp_pad_read2(); // Same helper as __pad_read2_d1.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Ce programme compare l’entrée ordinaire D0, l’extension D1 et le niveau numérique du microphone. Maintenez A sur la manette 1 et B sur la manette 2 jusqu’à la capture. NORMAL P1 et NORMAL P2 affichent 001 et 002. Dans l’exécution KUROSAKI fournie, D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL et MIC DIRECT affichent 000, car ces lignes ne sont pas émulées. Les valeurs restent ensuite fixes. Avec des périphériques adaptés et leurs lignes implémentées, les valeurs dépendent des entrées réelles ; interprétez-les selon les bits décrits ci-dessus au lieu d’attendre ces zéros.

Le modèle de contrôleurs du KUROSAKI publié ne renvoie que D0 et le bit 6 fixe. D1 et le bit du microphone valent donc toujours 0 dans cet émulateur, même lorsqu’un bouton ordinaire est maintenu. L’exécution fournie vérifie la compilation, l’entrée normale et cette observation de signal nul. Les signaux périphériques non nuls, les délais électriques et la compatibilité matérielle restent non vérifiés ; un zéro ici ne prouve ni le bon fonctionnement du périphérique ni l’impossibilité de le lire sur une machine réelle.

__exp_pad_read2: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:229

Notes de déclaration (texte source)
Alias the port-2 D1 controller reader; this is not an expansion-device discovery operation.
__far_memcpy

Sélectionne temporairement une banque ROM, copie len octets de src vers dst par adresses croissantes, puis rétablit la banque mémorisée par le compilateur. Sur cette plateforme, __farmemcpy et __far_memcpy utilisent la même implémentation.

Syntaxe

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

Paramètres

dst

Destination en mémoire accessible en écriture, assez grande pour tous les octets copiés. Hors transfert de zéro octet, les adresses nulles ou invalides ne sont pas vérifiées.

bank

Sélecteur de banque sur 8 bits pour la ROM source. Utilisez une banque compatible avec la cartouche et l’organisation du compilateur ; ce paramètre est distinct de l’adresse source.

src

Adresse CPU sur 16 bits des données dans le mappage ROM sélectionné. La déclaration FC attend un u16 ; convertissez le pointeur avec (u16)pointer.

len

Nombre d’octets, de 0 à 65535. Avec zéro, aucune mémoire n’est copiée, mais la sélection et le rétablissement de la banque ont bien lieu.

Valeur renvoyée

Aucune (void).

Conditions et limites

Toute la source doit être lisible dans le mappage ROM prévu, et toute la destination accessible en écriture. La copie ne passe pas automatiquement à la banque suivante, ne gère pas les chevauchements comme memmove et ne synchronise pas les écritures VRAM. Utilisez les API graphiques pour la VRAM.

Les exemples placent le code appelant dans la banque fixe 0 afin qu’un changement de banque source ne le rende pas inaccessible. Les transferts sont synchrones. N’interrompez pas un transfert par une autre copie changeant de banque ; cette fonction ne garantit pas à elle seule la sûreté des interruptions de toute l’application.

Exemple d’utilisation

c
__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);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Copie deux motifs, soit 32 octets, depuis la banque 2 alors que la banque 1 est sélectionnée au départ. Le programme compare tous les octets, vérifie le retour à la banque 1 et la conservation du témoin 0x55 lors d’une copie de longueur nulle.

Image attendue : une rangée rouge part de (16,32) et occupe 72×8 pixels. Elle répète trois fois un triangle de 8×8, un carré de 8×8 au contour d’un pixel et une tuile dont les quatre colonnes de gauche sont pleines. Le triangle s’élargit d’un pixel en haut à gauche à huit sur sa ligne inférieure.

Sur FC, la paire bleue en (16,64) montre __far_memcpy ; la verte en (16,96), asset_load_raw suivi d’un transfert en CHR. Chaque paire occupe 16×8 pixels sur fond noir. Ces rangées montrent les données graphiques copiées, pas seulement des valeurs d’état numériques.

L’état attendu est FAILED CHECKS 000 et FIRST FAILURE 00. La vérification exécute 300 images puis compare texte, formes et couleurs aux résultats attendus. Il s’agit d’une preuve sur émulateur, pas d’un essai sur matériel physique.

__far_memcpy: SUROM512
SUROM512

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:86

Notes de déclaration (texte source)
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

Appelle une fonction nommée sans argument. Le compilateur FC utilise sa banque d’affectation pour choisir un appel direct ou une routine qui restaure la banque. L’argument bank est évalué, mais le placement de la fonction détermine la sélection.

Syntaxe

c
__farcall(bank, function_name);

Paramètres

bank

Numéro de banque ROM non signé sur 8 bits. Pour un appel, il doit correspondre au placement de la fonction ; pour une lecture, il sélectionne la source. Respectez la plage du type de cartouche.

func

Nom déclaré d’une fonction sans paramètre. Cet appel intrinsèque par nom n’accepte pas une variable contenant une adresse de fonction. L’argument bank n’est pas transmis au rappel.

Valeur renvoyée

Le résultat suit le type de la fonction appelée. Un rappel void ne renvoie rien ; les rappels u8 et u16 renvoient leur valeur scalaire. L’exemple affiché utilise des rappels void.

Conditions et limites

Ces appels ne transmettent pas d’arguments utilisateur et ne rendent pas le rappel réentrant face aux interruptions. Gérez correctement ses données, sa pile et ses interruptions. La restauration des banques est testée avec des appels imbriqués et répétés ; l’exemple ne couvre ni le matériel physique ni tous les entrelacements d’interruptions.

Les banques FC sont des numéros logiques du compilateur. Zéro désigne la ROM commune, pas une cible de commutation ; la routine le ramène à la banque 1. SUROM512 accepte 1–30 : une constante hors plage transmise directement à l’intrinsèque de commutation provoque une erreur de compilation ; une valeur d’exécution invalide sélectionne 1 et active l’état de défaut du mapper. Lisez directement la ROM commune, sans demander la banque zéro.

Exemple d’utilisation

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

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

À ce stade du programme complet, __farcall porte callback_count à 2. Le rappel lit D3 dans la banque 3. Une lecture proche ultérieure de B2 dans home2 confirme le retour avec la banque 2 sélectionnée.

Le programme complet montre comment identifier des données par une banque et une adresse proche, appeler des rappels entre banques et distinguer l’état de la bibliothèque du mappage réel. Tous les nombres sont hexadécimaux. BANK IN et BANK OUT doivent valoir 02 ; BYTE HEX, D3 ; WORD HEX, 5C7A ; FAILED CHECKS et FIRST FAILURE, 00. Le texte est bleu sur CGB et FC, noir sur DMG.

CALL COUNT vaut 02 : la macro far_call et __farcall invoquent chacun le rappel une fois.

__farcall: MMC3
MMC3

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:83

Notes de déclaration (texte source)
Call a declared zero-argument function; placement selects its bank and its declared type determines the result.

kitaqfc/kitaqfc/CodeGenerator.cs:4131

csharp
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 est une autre orthographe de l’intrinsèque FC __far_memcpy. Les arguments et la routine de copie avec restauration de la banque sont identiques ; cette orthographe ne change ni la méthode ni la vitesse du transfert. L’exemple l’utilise pour lire des motifs de tuiles en ROM et les copier en RAM.

Sélectionne temporairement une banque ROM, copie len octets de src vers dst par adresses croissantes, puis rétablit la banque mémorisée par le compilateur. Sur cette plateforme, __farmemcpy et __far_memcpy utilisent la même implémentation.

Syntaxe

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

Paramètres

dst

Destination en mémoire accessible en écriture, assez grande pour tous les octets copiés. Hors transfert de zéro octet, les adresses nulles ou invalides ne sont pas vérifiées.

bank

Sélecteur de banque sur 8 bits pour la ROM source. Utilisez une banque compatible avec la cartouche et l’organisation du compilateur ; ce paramètre est distinct de l’adresse source.

src

Adresse CPU sur 16 bits des données dans le mappage ROM sélectionné. La déclaration FC attend un u16 ; convertissez le pointeur avec (u16)pointer.

len

Nombre d’octets, de 0 à 65535. Avec zéro, aucune mémoire n’est copiée, mais la sélection et le rétablissement de la banque ont bien lieu.

Valeur renvoyée

Aucune (void).

Conditions et limites

Le compilateur enregistre directement cette orthographe ; vous pouvez donc l’appeler sans déclaration distincte dans intrinsics.h. Les arguments sont, dans l’ordre, un pointeur RAM, un numéro de banque sur 8 bits, une adresse CPU sur 16 bits et un nombre d’octets sur 16 bits. src n’est pas un décalage depuis le début du fichier ROM.

Toute la source doit être lisible dans le mappage ROM prévu, et toute la destination accessible en écriture. La copie ne passe pas automatiquement à la banque suivante, ne gère pas les chevauchements comme memmove et ne synchronise pas les écritures VRAM. Utilisez les API graphiques pour la VRAM.

Les exemples placent le code appelant dans la banque fixe 0 afin qu’un changement de banque source ne le rende pas inaccessible. Les transferts sont synchrones. N’interrompez pas un transfert par une autre copie changeant de banque ; cette fonction ne garantit pas à elle seule la sûreté des interruptions de toute l’application.

Lorsque len vaut zéro, aucun octet de destination n’est modifié, mais la sélection et la restauration de la banque ont tout de même lieu. La fonction ne renvoie aucune valeur.

__far_memcpy

Exemple d’utilisation

c
__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);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Copie deux motifs, soit 32 octets, depuis la banque 2 alors que la banque 1 est sélectionnée au départ. Le programme compare tous les octets, vérifie le retour à la banque 1 et la conservation du témoin 0x55 lors d’une copie de longueur nulle.

Image attendue : une rangée rouge part de (16,32) et occupe 72×8 pixels. Elle répète trois fois un triangle de 8×8, un carré de 8×8 au contour d’un pixel et une tuile dont les quatre colonnes de gauche sont pleines. Le triangle s’élargit d’un pixel en haut à gauche à huit sur sa ligne inférieure.

Le triangle et le contour carré bleus au pixel (16,64) affichent les octets copiés par __farmemcpy. La paire verte en dessous, en (16,96), provient de asset_load_raw. Chaque paire occupe 16×8 pixels sur fond noir ; les octets copiés sont transférés en CHR RAM pour l’affichage.

L’état attendu est FAILED CHECKS 000 et FIRST FAILURE 00. La vérification exécute 300 images puis compare texte, formes et couleurs aux résultats attendus. Il s’agit d’une preuve sur émulateur, pas d’un essai sur matériel physique.

__farmemcpy: SUROM512 — Le triangle et le contour carré bleus au pixel (16,64) affichent les octets copiés par __farmemcpy. La paire verte en dessous, en (16,96), provient de asset_load_raw. Chaque paire occupe 16×8 pixels sur fond noir ; les octets copiés sont transférés en CHR RAM pour l’affichage.
SUROM512 — Le triangle et le contour carré bleus au pixel (16,64) affichent les octets copiés par __farmemcpy. La paire verte en dessous, en (16,96), provient de asset_load_raw. Chaque paire occupe 16×8 pixels sur fond noir ; les octets copiés sont transférés en CHR RAM pour l’affichage.

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/kitaqfc/CodeGenerator.cs:264

kitaqfc/kitaqfc/CodeGenerator.cs:4668

csharp
case "__far_memcpy":
                case "__farmemcpy":
                    return EmitKitaqfcHelperCall(funcName, args, ctx, new[] {2,1,2,2}, "__far_memcpy");
__farpeek16

Lit une valeur de 16 bits little-endian en addr et addr+1 après une sélection temporaire de banque, puis rétablit la banque précédente. C’est l’intrinsèque utilisée par far_data_read16.

Syntaxe

c
u16 __farpeek16(u8 bank, u16 addr);

Paramètres

bank

Numéro de banque ROM non signé sur 8 bits. Pour un appel, il doit correspondre au placement de la fonction ; pour une lecture, il sélectionne la source. Respectez la plage du type de cartouche.

addr

Adresse CPU sur 16 bits des données dans le mappage ROM sélectionné. La déclaration FC attend un u16 ; convertissez le pointeur avec (u16)pointer.

Valeur renvoyée

Le mot little-endian lu, sous forme de u16 (0–65535).

Conditions et limites

L’octet ou le mot entier doit être lisible dans le mappage CPU sélectionné. Ces lectures ne valident pas les pointeurs, ne bornent pas les adresses et ne passent pas automatiquement à une autre banque à la limite d’une fenêtre. Un mot exige deux octets consécutifs valides.

Les lectures et copies distantes sont synchrones et utilisent un espace de travail partagé du compilateur. Une interruption ne doit pas y exécuter un autre transfert pendant son utilisation. Sur GB, la copie utilise aussi un emplacement HRAM partagé pour la banque sauvegardée. L’exemple exécute ces opérations depuis du code fixe, sans gestionnaire de transfert concurrent.

Les banques FC sont des numéros logiques du compilateur. Zéro désigne la ROM commune, pas une cible de commutation ; la routine le ramène à la banque 1. SUROM512 accepte 1–30 : une constante hors plage transmise directement à l’intrinsèque de commutation provoque une erreur de compilation ; une valeur d’exécution invalide sélectionne 1 et active l’état de défaut du mapper. Lisez directement la ROM commune, sans demander la banque zéro.

Exemple d’utilisation

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

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Cet extrait utilise __farpeek16 pour lire payload+1 dans la banque 3 et vérifie 5C7A. Le programme complet lit aussi home2 avec un pointeur proche pour confirmer la restauration de la banque 2.

Le programme complet montre comment identifier des données par une banque et une adresse proche, appeler des rappels entre banques et distinguer l’état de la bibliothèque du mappage réel. Tous les nombres sont hexadécimaux. BANK IN et BANK OUT doivent valoir 02 ; BYTE HEX, D3 ; WORD HEX, 5C7A ; FAILED CHECKS et FIRST FAILURE, 00. Le texte est bleu sur CGB et FC, noir sur DMG.

CALL COUNT vaut 02 : la macro far_call et __farcall invoquent chacun le rappel une fois.

__farpeek16: MMC3
MMC3

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:90

Notes de déclaration (texte source)
Temporarily select bank and read a little-endian word, then restore the previous mapping.

kitaqfc/kitaqfc/CodeGenerator.cs:8698

csharp
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

Sélectionne temporairement la banque indiquée, lit un octet à l’adresse CPU et rétablit la banque précédente. C’est l’intrinsèque utilisée par far_data_read8.

Syntaxe

c
u8 __farpeek8(u8 bank, u16 addr);

Paramètres

bank

Numéro de banque ROM non signé sur 8 bits. Pour un appel, il doit correspondre au placement de la fonction ; pour une lecture, il sélectionne la source. Respectez la plage du type de cartouche.

addr

Adresse CPU sur 16 bits des données dans le mappage ROM sélectionné. La déclaration FC attend un u16 ; convertissez le pointeur avec (u16)pointer.

Valeur renvoyée

L’octet lu, sous forme de u8 (0–255).

Conditions et limites

L’octet ou le mot entier doit être lisible dans le mappage CPU sélectionné. Ces lectures ne valident pas les pointeurs, ne bornent pas les adresses et ne passent pas automatiquement à une autre banque à la limite d’une fenêtre. Un mot exige deux octets consécutifs valides.

Les lectures et copies distantes sont synchrones et utilisent un espace de travail partagé du compilateur. Une interruption ne doit pas y exécuter un autre transfert pendant son utilisation. Sur GB, la copie utilise aussi un emplacement HRAM partagé pour la banque sauvegardée. L’exemple exécute ces opérations depuis du code fixe, sans gestionnaire de transfert concurrent.

Les banques FC sont des numéros logiques du compilateur. Zéro désigne la ROM commune, pas une cible de commutation ; la routine le ramène à la banque 1. SUROM512 accepte 1–30 : une constante hors plage transmise directement à l’intrinsèque de commutation provoque une erreur de compilation ; une valeur d’exécution invalide sélectionne 1 et active l’état de défaut du mapper. Lisez directement la ROM commune, sans demander la banque zéro.

Exemple d’utilisation

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

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Cet extrait utilise __farpeek8 pour lire payload dans la banque 3 et vérifie D3. Le programme complet lit aussi home2 avec un pointeur proche pour confirmer la restauration de la banque 2.

Le programme complet montre comment identifier des données par une banque et une adresse proche, appeler des rappels entre banques et distinguer l’état de la bibliothèque du mappage réel. Tous les nombres sont hexadécimaux. BANK IN et BANK OUT doivent valoir 02 ; BYTE HEX, D3 ; WORD HEX, 5C7A ; FAILED CHECKS et FIRST FAILURE, 00. Le texte est bleu sur CGB et FC, noir sur DMG.

CALL COUNT vaut 02 : la macro far_call et __farcall invoquent chacun le rappel une fois.

__farpeek8: MMC3
MMC3

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:88

Notes de déclaration (texte source)
Temporarily select bank, read one byte at addr and restore the prior bank mapping.

kitaqfc/kitaqfc/CodeGenerator.cs:8683

csharp
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

Vérifiez le motif de détection du clavier avant d’interpréter les touches enfoncées.

Syntaxe

c
u8 __fkb_detect(void);

Valeur renvoyée

Renvoie 1 si la ligne 9, colonne 0 donne 0x1E et si la matrice désactivée donne zéro ; sinon, renvoie 0. Il s’agit d’une détection heuristique par motif de signal, pas d’un code d’identification du périphérique.

Conditions et limites

Ces API accèdent directement à $4016/$4017. N’entrelacez pas sur les mêmes ports des opérations de manette, clavier ou série pilotées par interruption. Les positions de décalage des contrôleurs peuvent aussi être affectées. L’exemple désactive la NMI pendant la capture des entrées, puis l’active à l’initialisation de l’affichage.

Écrit 5 dans $4016 pour revenir à la ligne 0, puis avance les lignes par transitions haut-bas du bit de sélection de colonne. Outre les instructions environnantes, le code généré inclut 12 cycles CPU de NOP après la réinitialisation et 50 après la sélection de colonne. Il écrit zéro à la sortie pour désactiver la matrice.

Une vérification de régression distincte examine les lignes 0 à 9, les deux colonnes, le balayage de neuf lignes, les délais de stabilisation et l’état final des ports à partir de traces de bus CPU exécutées. Elle n’injecte aucun signal de touche physique.

La ligne 9 ne contient aucune touche. Une condition non satisfaite provoque un retour immédiat de zéro. KUROSAKI renvoie zéro dès la première lecture ; la détection y échoue donc.

Ces images exécutent les ROM téléchargeables sans modification dans KUROSAKI. Son bus d’entrée modélise les manettes, mais pas les signaux du clavier, du pistolet optique ou d’un adaptateur série. Les bits d’entrée externes se lisent à zéro ; les captures ne valident donc pas ces appareils. Les touches physiques, la détection optique et la communication restent non testées.

NESdev : clavier Family BASIC

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Dans cette exécution KUROSAKI, DEVICE DETECT, ROW 6 COL 0 et A KEY - GUARDED valent tous 000 ; les neuf paires COL0/COL1 valent 00. A se trouve ligne 6, colonne 0, bit 4, mais l’échec de détection empêche de signaler le zéro brut comme une touche enfoncée. Le tableau est une capture unique au démarrage.

__fkb_detect: KEYBOARD-DEFAULT — Dans cette exécution KUROSAKI, DEVICE DETECT, ROW 6 COL 0 et A KEY - GUARDED valent tous 000 ; les neuf paires COL0/COL1 valent 00. A se trouve ligne 6, colonne 0, bit 4, mais l’échec de détection empêche de signaler le zéro brut comme une touche enfoncée. Le tableau est une capture unique au démarrage.
KEYBOARD-DEFAULT — Dans cette exécution KUROSAKI, DEVICE DETECT, ROW 6 COL 0 et A KEY - GUARDED valent tous 000 ; les neuf paires COL0/COL1 valent 00. A se trouve ligne 6, colonne 0, bit 4, mais l’échec de détection empêche de signaler le zéro brut comme une touche enfoncée. Le tableau est une capture unique au démarrage.

Programme d’exemple complet

Commande de compilation

powershell
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.png
Implémentation et source

kitaqfc/lib/intrinsics.h:259

Notes de déclaration (texte source)
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

csharp
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

Lisez une seule ligne et un seul groupe de colonnes, sans tampon de 18 octets, lorsque seul ce groupe vous intéresse.

Syntaxe

c
u8 __fkb_read_row_col(u8 row, u8 col);

Paramètres

row

Utilisez 0..8 pour les lignes de touches, ou 9 pour la ligne de détection sans touche. Aucun contrôle de plage n’est généré à l’exécution.

col

0 ou 1. Seul le bit de poids faible est utilisé : une valeur paire sélectionne le groupe 0, une valeur impaire le groupe 1.

Valeur renvoyée

Renvoie $4017 & 0x1E sans décaler les bits vers les positions de poids faible.

Conditions et limites

Ces API accèdent directement à $4016/$4017. N’entrelacez pas sur les mêmes ports des opérations de manette, clavier ou série pilotées par interruption. Les positions de décalage des contrôleurs peuvent aussi être affectées. L’exemple désactive la NMI pendant la capture des entrées, puis l’active à l’initialisation de l’affichage.

Les groupes de touches sont les bits bruts 1 à 4 : une touche enfoncée efface son bit, et un groupe au repos vaut 0x1E. Ce ne sont ni du texte ASCII ni un masque où un signifie enfoncé. Un appareil débranché peut aussi se lire à zéro ; vérifiez sa présence avant d’interpréter cela comme toutes les touches maintenues.

Écrit 5 dans $4016 pour revenir à la ligne 0, puis avance les lignes par transitions haut-bas du bit de sélection de colonne. Outre les instructions environnantes, le code généré inclut 12 cycles CPU de NOP après la réinitialisation et 50 après la sélection de colonne. Il écrit zéro à la sortie pour désactiver la matrice.

Une vérification de régression distincte examine les lignes 0 à 9, les deux colonnes, le balayage de neuf lignes, les délais de stabilisation et l’état final des ports à partir de traces de bus CPU exécutées. Elle n’injecte aucun signal de touche physique.

Revient à la ligne 0 et avance à nouveau à chaque appel ; la position atteinte lors de l’appel précédent n’est pas réutilisée.

Ces images exécutent les ROM téléchargeables sans modification dans KUROSAKI. Son bus d’entrée modélise les manettes, mais pas les signaux du clavier, du pistolet optique ou d’un adaptateur série. Les bits d’entrée externes se lisent à zéro ; les captures ne valident donc pas ces appareils. Les touches physiques, la détection optique et la communication restent non testées.

NESdev : clavier Family BASIC

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Dans cette exécution KUROSAKI, DEVICE DETECT, ROW 6 COL 0 et A KEY - GUARDED valent tous 000 ; les neuf paires COL0/COL1 valent 00. A se trouve ligne 6, colonne 0, bit 4, mais l’échec de détection empêche de signaler le zéro brut comme une touche enfoncée. Le tableau est une capture unique au démarrage.

__fkb_read_row_col: KEYBOARD-DEFAULT — Dans cette exécution KUROSAKI, DEVICE DETECT, ROW 6 COL 0 et A KEY - GUARDED valent tous 000 ; les neuf paires COL0/COL1 valent 00. A se trouve ligne 6, colonne 0, bit 4, mais l’échec de détection empêche de signaler le zéro brut comme une touche enfoncée. Le tableau est une capture unique au démarrage.
KEYBOARD-DEFAULT — Dans cette exécution KUROSAKI, DEVICE DETECT, ROW 6 COL 0 et A KEY - GUARDED valent tous 000 ; les neuf paires COL0/COL1 valent 00. A se trouve ligne 6, colonne 0, bit 4, mais l’échec de détection empêche de signaler le zéro brut comme une touche enfoncée. Le tableau est une capture unique au démarrage.

Programme d’exemple complet

Commande de compilation

powershell
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.png
Implémentation et source

kitaqfc/lib/intrinsics.h:264

Notes de déclaration (texte source)
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

csharp
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

Balayez les neuf lignes et les deux groupes de colonnes dans un tampon de 18 octets afin d’examiner plusieurs touches à partir d’une même capture de la matrice.

Syntaxe

c
void __fkb_scan(u8* dst18);

Paramètres

dst18

Zone inscriptible d’au moins 18 octets. L’élément row*2+col reçoit la valeur brute du groupe correspondant. Aucun contrôle du pointeur nul ni de la longueur n’est effectué.

Valeur renvoyée

Aucune valeur de retour.

Conditions et limites

Ces API accèdent directement à $4016/$4017. N’entrelacez pas sur les mêmes ports des opérations de manette, clavier ou série pilotées par interruption. Les positions de décalage des contrôleurs peuvent aussi être affectées. L’exemple désactive la NMI pendant la capture des entrées, puis l’active à l’initialisation de l’affichage.

Les groupes de touches sont les bits bruts 1 à 4 : une touche enfoncée efface son bit, et un groupe au repos vaut 0x1E. Ce ne sont ni du texte ASCII ni un masque où un signifie enfoncé. Un appareil débranché peut aussi se lire à zéro ; vérifiez sa présence avant d’interpréter cela comme toutes les touches maintenues.

Écrit 5 dans $4016 pour revenir à la ligne 0, puis avance les lignes par transitions haut-bas du bit de sélection de colonne. Outre les instructions environnantes, le code généré inclut 12 cycles CPU de NOP après la réinitialisation et 50 après la sélection de colonne. Il écrit zéro à la sortie pour désactiver la matrice.

Une vérification de régression distincte examine les lignes 0 à 9, les deux colonnes, le balayage de neuf lignes, les délais de stabilisation et l’état final des ports à partir de traces de bus CPU exécutées. Elle n’injecte aucun signal de touche physique.

Lit la colonne 0 puis la colonne 1 de chaque ligne. Le balayage est séquentiel ; ce n’est pas un instantané simultané de toutes les touches. Un groupe à zéro n’interrompt pas le balayage.

Ces images exécutent les ROM téléchargeables sans modification dans KUROSAKI. Son bus d’entrée modélise les manettes, mais pas les signaux du clavier, du pistolet optique ou d’un adaptateur série. Les bits d’entrée externes se lisent à zéro ; les captures ne valident donc pas ces appareils. Les touches physiques, la détection optique et la communication restent non testées.

NESdev : clavier Family BASIC

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Dans cette exécution KUROSAKI, DEVICE DETECT, ROW 6 COL 0 et A KEY - GUARDED valent tous 000 ; les neuf paires COL0/COL1 valent 00. A se trouve ligne 6, colonne 0, bit 4, mais l’échec de détection empêche de signaler le zéro brut comme une touche enfoncée. Le tableau est une capture unique au démarrage.

__fkb_scan: KEYBOARD-DEFAULT — Dans cette exécution KUROSAKI, DEVICE DETECT, ROW 6 COL 0 et A KEY - GUARDED valent tous 000 ; les neuf paires COL0/COL1 valent 00. A se trouve ligne 6, colonne 0, bit 4, mais l’échec de détection empêche de signaler le zéro brut comme une touche enfoncée. Le tableau est une capture unique au démarrage.
KEYBOARD-DEFAULT — Dans cette exécution KUROSAKI, DEVICE DETECT, ROW 6 COL 0 et A KEY - GUARDED valent tous 000 ; les neuf paires COL0/COL1 valent 00. A se trouve ligne 6, colonne 0, bit 4, mais l’échec de détection empêche de signaler le zéro brut comme une touche enfoncée. Le tableau est une capture unique au démarrage.

Programme d’exemple complet

Commande de compilation

powershell
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.png
Implémentation et source

kitaqfc/lib/intrinsics.h:261

Notes de déclaration (texte source)
Write 18 raw masked samples (4017 & 1E), two per row, to caller storage; reset 4016 afterward.

kitaqfc/kitaqfc/CodeGenerator.cs:7773

csharp
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

Émet SEI pour positionner le bit I du registre d’état du processeur et masquer les requêtes IRQ ordinaires. Sert aux sections critiques vis-à-vis des IRQ. Ne désactive pas la NMI, ne coupe pas le son de l’APU, n’acquitte pas la source IRQ et ne sauvegarde pas le masque précédent.

Syntaxe

c
void __irq_disable(void);

Valeur renvoyée

Aucune (void).

Conditions et limites

Le bit I ne contrôle que les IRQ ordinaires : la NMI peut interrompre cette section. Masquer les IRQ n’acquitte aucune requête de périphérique et ne protège pas les données partagées avec la NMI. Préparez séparément sources et gestionnaires. L’exemple ne teste aucun périphérique générant des IRQ.

NESdev: NMI

NESdev : interruptions du CPU

Exemple d’utilisation

c
__irq_disable();
disabled=(__irq_save()&4)>>2; // The CPU I bit must be 1.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme distingue le masque IRQ du processeur du contrôle NMI de la PPU. En bleu sur fond noir doivent apparaître IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 et RESTORED I = 000. Chaque valeur représente le bit I extrait, pas l’octet d’état complet. FAILED CHECKS doit valoir 000.

Exécutez au moins 360 images pour terminer la démonstration des 256 attentes. NMI DELTA 3 WAITS doit valoir 003 même avec les IRQ masquées ; NMI DELTA 256 WAITS doit valoir 000, car le compteur sur un octet boucle. NMI OFF DELTA doit aussi valoir 000. Le programme utilise le gestionnaire par défaut sans runtime.c. Vingt et un contrôles d’état distincts couvrent notamment la restauration des indicateurs et des attentes observées sur une durée limitée avec NMI désactivée ou un gestionnaire personnalisé vide. KUROSAKI/NROM est vérifié, pas le matériel réel.

__irq_disable: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:176

Notes de déclaration (texte source)
Emit SEI to mask CPU IRQs; NMI is unaffected.

kitaqfc/kitaqfc/CodeGenerator.cs:4733

csharp
case "__irq_disable": if (!Need(0)) return true; EmitAsm("SEI"); return true;
__irq_enable

Émet CLI pour effacer le bit I du registre d’état du processeur et autoriser le traitement des IRQ ordinaires. Ne configure aucune source d’interruption du mapper ou de l’APU. Préparez le gestionnaire et l’acquittement de la requête avant d’activer cette source.

Syntaxe

c
void __irq_enable(void);

Valeur renvoyée

Aucune (void).

Conditions et limites

Le bit I ne contrôle que les IRQ ordinaires : la NMI peut interrompre cette section. Masquer les IRQ n’acquitte aucune requête de périphérique et ne protège pas les données partagées avec la NMI. Préparez séparément sources et gestionnaires. L’exemple ne teste aucun périphérique générant des IRQ.

NESdev: NMI

NESdev : interruptions du CPU

Exemple d’utilisation

c
__irq_enable();
enabled=(__irq_save()&4)>>2; // Save returns the old I bit: 0, then masks IRQ.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme distingue le masque IRQ du processeur du contrôle NMI de la PPU. En bleu sur fond noir doivent apparaître IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 et RESTORED I = 000. Chaque valeur représente le bit I extrait, pas l’octet d’état complet. FAILED CHECKS doit valoir 000.

Exécutez au moins 360 images pour terminer la démonstration des 256 attentes. NMI DELTA 3 WAITS doit valoir 003 même avec les IRQ masquées ; NMI DELTA 256 WAITS doit valoir 000, car le compteur sur un octet boucle. NMI OFF DELTA doit aussi valoir 000. Le programme utilise le gestionnaire par défaut sans runtime.c. Vingt et un contrôles d’état distincts couvrent notamment la restauration des indicateurs et des attentes observées sur une durée limitée avec NMI désactivée ou un gestionnaire personnalisé vide. KUROSAKI/NROM est vérifié, pas le matériel réel.

__irq_enable: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:178

Notes de déclaration (texte source)
Emit CLI to unmask CPU IRQs; the caller must have a valid interrupt handler.

kitaqfc/kitaqfc/CodeGenerator.cs:4734

csharp
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

Évalue l’octet d’état fourni, l’empile avec PHA et restaure l’état du processeur avec PLP. Rétablit le masque IRQ et les indicateurs arithmétiques sauvegardés, sans autoriser systématiquement les IRQ. Les expressions C exécutées ensuite peuvent modifier à nouveau les indicateurs arithmétiques.

Syntaxe

c
void __irq_restore(u8 state);

Paramètres

state

L’état complet du processeur sur 8 bits, capturé par __irq_save. Conservez-le pour __irq_restore sans le convertir en booléen. Le bit 2 est I : 1 masque les IRQ, 0 les autorise. La représentation empilée par PHP positionne les bits 4 et 5.

Valeur renvoyée

Aucune (void).

Conditions et limites

Le bit I ne contrôle que les IRQ ordinaires : la NMI peut interrompre cette section. Masquer les IRQ n’acquitte aucune requête de périphérique et ne protège pas les données partagées avec la NMI. Préparez séparément sources et gestionnaires. L’exemple ne teste aucun périphérique générant des IRQ.

NESdev: NMI

NESdev : interruptions du CPU

Exemple d’utilisation

c
__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.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme distingue le masque IRQ du processeur du contrôle NMI de la PPU. En bleu sur fond noir doivent apparaître IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 et RESTORED I = 000. Chaque valeur représente le bit I extrait, pas l’octet d’état complet. FAILED CHECKS doit valoir 000.

Exécutez au moins 360 images pour terminer la démonstration des 256 attentes. NMI DELTA 3 WAITS doit valoir 003 même avec les IRQ masquées ; NMI DELTA 256 WAITS doit valoir 000, car le compteur sur un octet boucle. NMI OFF DELTA doit aussi valoir 000. Le programme utilise le gestionnaire par défaut sans runtime.c. Vingt et un contrôles d’état distincts couvrent notamment la restauration des indicateurs et des attentes observées sur une durée limitée avec NMI désactivée ou un gestionnaire personnalisé vide. KUROSAKI/NROM est vérifié, pas le matériel réel.

__irq_restore: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:182

Notes de déclaration (texte source)
Restore processor status with PLP, including arithmetic flags and the saved IRQ mask.

kitaqfc/kitaqfc/CodeGenerator.cs:4737

csharp
case "__irq_restore": if (!Need(1)) return true; Load1(args[0]); EmitAsm("PHA"); EmitAsm("PLP"); return true;
__irq_save

Sauvegarde l’octet d’état du processeur avec PHP, masque les IRQ avec SEI, puis renvoie l’octet sauvegardé via PLA. Le bit I sauvegardé indique l’état avant le masquage. Associez cet appel à __irq_restore pour qu’une section critique imbriquée conserve un masquage IRQ déjà actif.

Syntaxe

c
u8 __irq_save(void);

Valeur renvoyée

L’état complet du processeur sur 8 bits, capturé par __irq_save. Conservez-le pour __irq_restore sans le convertir en booléen. Le bit 2 est I : 1 masque les IRQ, 0 les autorise. La représentation empilée par PHP positionne les bits 4 et 5.

Conditions et limites

Le bit I ne contrôle que les IRQ ordinaires : la NMI peut interrompre cette section. Masquer les IRQ n’acquitte aucune requête de périphérique et ne protège pas les données partagées avec la NMI. Préparez séparément sources et gestionnaires. L’exemple ne teste aucun périphérique générant des IRQ.

NESdev: NMI

NESdev : interruptions du CPU

Exemple d’utilisation

c
saved_state=__irq_save(); // Save all processor-status bits, then set I.
inside=(__irq_save()&4)>>2; // IRQ remains masked inside this section.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme distingue le masque IRQ du processeur du contrôle NMI de la PPU. En bleu sur fond noir doivent apparaître IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 et RESTORED I = 000. Chaque valeur représente le bit I extrait, pas l’octet d’état complet. FAILED CHECKS doit valoir 000.

Exécutez au moins 360 images pour terminer la démonstration des 256 attentes. NMI DELTA 3 WAITS doit valoir 003 même avec les IRQ masquées ; NMI DELTA 256 WAITS doit valoir 000, car le compteur sur un octet boucle. NMI OFF DELTA doit aussi valoir 000. Le programme utilise le gestionnaire par défaut sans runtime.c. Vingt et un contrôles d’état distincts couvrent notamment la restauration des indicateurs et des attentes observées sur une durée limitée avec NMI désactivée ou un gestionnaire personnalisé vide. KUROSAKI/NROM est vérifié, pas le matériel réel.

__irq_save: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:180

Notes de déclaration (texte source)
Return the processor-status byte captured before SEI. Pass that byte to __irq_restore.

kitaqfc/kitaqfc/CodeGenerator.cs:4736

csharp
case "__irq_save": if (!Need(0)) return true; EmitAsm("PHP"); EmitAsm("SEI"); EmitAsm("PLA"); return true;
__irq_scanline_set

Alias de l’opération MMC3 de verrouillage/rechargement de __mapper_irq_set. Le mot scanline ne garantit pas une ligne d’écran absolue.

Syntaxe

c
void __irq_scanline_set(u8 line);

Paramètres

line

Valeur de verrouillage du compteur sur huit bits, de 0 à 255, et non une coordonnée Y absolue. Le moment du rechargement et les transitions PPU A12 admissibles déterminent le déclenchement. Zéro est valide et ne signifie pas « désactiver les interruptions ».

Valeur renvoyée

Aucune valeur de retour.

Conditions et limites

Les déclarations des intrinsèques sont dans intrinsics.h. Incluez nes_game.h pour les macros nes_mapper_irq_*. Choisissez la carte à la compilation avec --mapper.

MMC3 uniquement. Fournissez un gestionnaire __nes_irq, des transitions PPU A12 admissibles, l’activation IRQ du mapper et __irq_enable() côté CPU. L’exemple active le rendu avec les motifs de fond à $0000 et les sprites à $1000. Cette leçon ne vérifie pas la synchronisation sur une ligne exacte du matériel réel.

L’acquittement et la désactivation MMC3 écrivent tous deux dans $E000. Cela efface la demande actuelle et désactive les suivantes. Rappelez __mapper_irq_enable() au moment voulu pour armer une autre interruption. Aucune de ces opérations ne modifie le masque d’interruptions CPU ni l’activation NMI.

Le compilateur émet RTI pour un gestionnaire d’interruption personnalisé, mais celui-ci doit préserver les registres A/X/Y et les zones temporaires partagées qu’il modifie. L’exemple sauvegarde A/X/Y et n’utilise qu’un incrément d’octet et un acquittement sans argument. Si vous ajoutez des appels C, examinez aussi leur stockage partagé.

Exemple d’utilisation

c
irq_phase=2;
    __irq_scanline_set(7); __mapper_irq_enable();
    for (i=0;i<6;i++) { m_wait(); }
    mapper_result[17]=irq_count;

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple MMC3 affiche les marqueurs CHR 19/20, 21/22 et 23/24. Les lectures CIRAM donnent A B A B en mirroring vertical et A A B B en horizontal. Les comptes IRQ sont 01→02→03→03 : chacune des trois premières activations explicites autorise une interruption ; le compteur reste fixe après désactivation. Chaque étape attend six images.

__irq_scanline_set: MMC3-DEFAULT
MMC3-DEFAULT

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:324

Notes de déclaration (texte source)
Write the MMC3 IRQ latch and reload registers. This does not enable IRQ delivery.
__joypad2p_voice

__joypad2p_voice est un autre nom d’entrée de la routine __mic_read2p. Les deux lisent le même bit numérique du microphone. « Voice » n’implique aucun traitement audio : chaque appel prend un échantillon distinct, qui peut différer si le signal change entre les appels.

Lit le signal numérique du microphone de la seconde manette Famicom dans le bit 2 de $4016. La routine lit le registre une fois, applique le masque 0x04 et normalise à 0 ou 1 sans inversion. Elle ne verrouille pas les états des manettes, n’enregistre pas de son, ne mesure pas le volume et ne reconnaît pas la parole.

Syntaxe

c
u8 __joypad2p_voice(void);

Valeur renvoyée

Exactement 1 si $4016 & 0x04 est non nul, sinon 0. Il s’agit du niveau numérique échantillonné, pas d’un masque de boutons, d’une amplitude, d’une fréquence ou d’un nombre de mots prononcés.

Conditions et limites

Bien que le microphone appartienne à la manette 2, cette routine accède à $4016, pas à $4017. Une lecture du registre peut aussi faire avancer le flux série de la manette lorsque le strobe est bas ; n’insérez donc pas de lecture du microphone dans une autre boucle de lecture de huit bits. Aucun historique d’entrée mémorisé n’est mis à jour.

Le modèle de contrôleurs du KUROSAKI publié ne renvoie que D0 et le bit 6 fixe. D1 et le bit du microphone valent donc toujours 0 dans cet émulateur, même lorsqu’un bouton ordinaire est maintenu. L’exécution fournie vérifie la compilation, l’entrée normale et cette observation de signal nul. Les signaux périphériques non nuls, les délais électriques et la compatibilité matérielle restent non vérifiés ; un zéro ici ne prouve ni le bon fonctionnement du périphérique ni l’impossibilité de le lire sur une machine réelle.

Exemple d’utilisation

c
voice=__joypad2p_voice();

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Ce programme compare l’entrée ordinaire D0, l’extension D1 et le niveau numérique du microphone. Maintenez A sur la manette 1 et B sur la manette 2 jusqu’à la capture. NORMAL P1 et NORMAL P2 affichent 001 et 002. Dans l’exécution KUROSAKI fournie, D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL et MIC DIRECT affichent 000, car ces lignes ne sont pas émulées. Les valeurs restent ensuite fixes. Avec des périphériques adaptés et leurs lignes implémentées, les valeurs dépendent des entrées réelles ; interprétez-les selon les bits décrits ci-dessus au lieu d’attendre ces zéros.

Le modèle de contrôleurs du KUROSAKI publié ne renvoie que D0 et le bit 6 fixe. D1 et le bit du microphone valent donc toujours 0 dans cet émulateur, même lorsqu’un bouton ordinaire est maintenu. L’exécution fournie vérifie la compilation, l’entrée normale et cette observation de signal nul. Les signaux périphériques non nuls, les délais électriques et la compatibilité matérielle restent non vérifiés ; un zéro ici ne prouve ni le bon fonctionnement du périphérique ni l’impossibilité de le lire sur une machine réelle.

__joypad2p_voice: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:231

Notes de déclaration (texte source)
Return controller-port 4016 bit 2 normalized to 0 or 1; this is a microphone level bit, not PCM audio.
__mac16

Calcule une multiplication-accumulation non signée.

Syntaxe

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

Paramètres

acc

Accumulateur sur 16 bits à ajouter.

a

Mot non signé de 0 à 65535.

b

Coefficient non signé de 0 à 255.

Valeur renvoyée

Renvoie les 16 bits de poids faible de acc+a*b.

Conditions et limites

Sur FC, les fonctions nommées q8_8/q1_7 utilisent les produits entiers bruts, sans décalage de mise à l’échelle fixe. Les fonctions Q1.7 sont des alias des opérations signées correspondantes. L’appelant gère les unités ; les résultats signés sont renvoyés comme motifs de bits u16.

Exemple d’utilisation

c
// 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);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Comparez les entrées littérales aux valeurs en RAM. Les résultats représentatifs sont affichés sur 16 bits non signés ; les négatifs utilisent le complément à deux. Le GB remet à l’échelle les produits à virgule fixe, tandis que le FC renvoie des produits entiers : des entrées identiques ne donnent donc pas nécessairement les mêmes résultats.

L’extrait stocke les valeurs suivantes sur 16 bits : result[8]=65115, result[9]=65115. Chaque calcul avec une entrée littérale correspond à son équivalent avec une entrée en RAM. L’écran affiche des valeurs représentatives ; le tableau result conserve tous les résultats.

__mac16: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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.chr
Implémentation et source

kitaqfc/lib/intrinsics.h:55

Notes de déclaration (texte source)
Return acc+a*b modulo 65536 using unsigned multiplication.

kitaqfc/kitaqfc/CodeGenerator.cs:4650

csharp
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

Additionne les différences absolues en X et en Y entre deux points.

Syntaxe

c
u8 __manhattan(u8 x1, u8 y1, u8 x2, u8 y2);

Paramètres

x1

Coordonnée X du premier point, de 0 à 255.

y1

Coordonnée Y du premier point, de 0 à 255.

x2

Coordonnée X du second point, de 0 à 255.

y2

Coordonnée Y du second point, de 0 à 255.

Valeur renvoyée

Renvoie l’octet de poids faible de |x1−x2|+|y1−y2| ; les distances supérieures à 255 rebouclent.

Exemple d’utilisation

c
result[5]=__manhattan(2,3,9,11);result[6]=__manhattan(0,0,255,255);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Testez intérieur, limites, point extérieur et largeur nulle : INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1. Vérifiez la distance 15, la distance rebouclée 254 et les indices de tableau 64/65280.

__manhattan: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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.chr
Implémentation et source

kitaqfc/lib/intrinsics.h:33

Notes de déclaration (texte source)
Return abs(x1-x2)+abs(y1-y2) modulo 256; large distances are not saturated.

kitaqfc/kitaqfc/CodeGenerator.cs:4629

csharp
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

Convertit les coordonnées d’une case en indice de tableau rangé par lignes.

Syntaxe

c
u16 __map_index(u8 x, u8 y, u8 width);

Paramètres

x

Colonne de 0 à 255, normalement inférieure à width.

y

Ligne de 0 à 255.

width

Nombre de cases par ligne, de 0 à 255.

Valeur renvoyée

Renvoie y*width+x sous forme de u16 ; ne valide pas les coordonnées.

Exemple d’utilisation

c
result[7]=__map_index(4,3,20);result[8]=__map_index(255,255,255);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Testez intérieur, limites, point extérieur et largeur nulle : INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1. Vérifiez la distance 15, la distance rebouclée 254 et les indices de tableau 64/65280.

__map_index: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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.chr
Implémentation et source

kitaqfc/lib/intrinsics.h:35

Notes de déclaration (texte source)
Return y*width+x as a word without checking coordinates against map dimensions.

kitaqfc/kitaqfc/CodeGenerator.cs:4631

csharp
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

Renvoie le numéro de mapper choisi à la compilation, pour l’initialisation ou l’affichage propres à la carte.

Syntaxe

c
u8 __mapper_id(void);

Valeur renvoyée

Numéro de mapper sur huit bits : NROM=0, MMC1=1, UxROM=2, CNROM=3, MMC3=4, MMC5=5, AxROM=7, VRC6=24, FME7=69 et VRC7=85. Le compilateur émet une constante ; il n’interroge pas le matériel en fonctionnement.

Conditions et limites

Les déclarations des intrinsèques sont dans intrinsics.h. Incluez nes_game.h pour les macros nes_mapper_irq_*. Choisissez la carte à la compilation avec --mapper.

Exemple d’utilisation

c
mapper_result[0] = __mapper_id();

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple MMC3 affiche les marqueurs CHR 19/20, 21/22 et 23/24. Les lectures CIRAM donnent A B A B en mirroring vertical et A A B B en horizontal. Les comptes IRQ sont 01→02→03→03 : chacune des trois premières activations explicites autorise une interruption ; le compteur reste fixe après désactivation. Chaque étape attend six images.

__mapper_id: MMC3-DEFAULT
MMC3-DEFAULT

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:314

Notes de déclaration (texte source)
Mapper
Return the low byte of the mapper number selected for this build; no cartridge probe is performed.
__mapper_irq_ack

Acquitte MMC3 dans le gestionnaire IRQ. Comme l’opération écrit dans $E000, elle désactive aussi les demandes IRQ suivantes.

Syntaxe

c
void __mapper_irq_ack(void);

Valeur renvoyée

Aucune valeur de retour.

Conditions et limites

Les déclarations des intrinsèques sont dans intrinsics.h. Incluez nes_game.h pour les macros nes_mapper_irq_*. Choisissez la carte à la compilation avec --mapper.

MMC3 uniquement. Fournissez un gestionnaire __nes_irq, des transitions PPU A12 admissibles, l’activation IRQ du mapper et __irq_enable() côté CPU. L’exemple active le rendu avec les motifs de fond à $0000 et les sprites à $1000. Cette leçon ne vérifie pas la synchronisation sur une ligne exacte du matériel réel.

L’acquittement et la désactivation MMC3 écrivent tous deux dans $E000. Cela efface la demande actuelle et désactive les suivantes. Rappelez __mapper_irq_enable() au moment voulu pour armer une autre interruption. Aucune de ces opérations ne modifie le masque d’interruptions CPU ni l’activation NMI.

Le compilateur émet RTI pour un gestionnaire d’interruption personnalisé, mais celui-ci doit préserver les registres A/X/Y et les zones temporaires partagées qu’il modifie. L’exemple sauvegarde A/X/Y et n’utilise qu’un incrément d’octet et un acquittement sans argument. Si vous ajoutez des appels C, examinez aussi leur stockage partagé.

Exemple d’utilisation

c
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
    }
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple MMC3 affiche les marqueurs CHR 19/20, 21/22 et 23/24. Les lectures CIRAM donnent A B A B en mirroring vertical et A A B B en horizontal. Les comptes IRQ sont 01→02→03→03 : chacune des trois premières activations explicites autorise une interruption ; le compteur reste fixe après désactivation. Chaque étape attend six images.

__mapper_irq_ack: MMC3-DEFAULT
MMC3-DEFAULT

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:332

Notes de déclaration (texte source)
Use the same E000 write as disable; explicitly re-enable when another interrupt is needed.
__mapper_irq_disable

Écrit dans $E000 pour effacer la demande IRQ MMC3 et désactiver les suivantes.

Syntaxe

c
void __mapper_irq_disable(void);

Valeur renvoyée

Aucune valeur de retour.

Conditions et limites

Les déclarations des intrinsèques sont dans intrinsics.h. Incluez nes_game.h pour les macros nes_mapper_irq_*. Choisissez la carte à la compilation avec --mapper.

MMC3 uniquement. Fournissez un gestionnaire __nes_irq, des transitions PPU A12 admissibles, l’activation IRQ du mapper et __irq_enable() côté CPU. L’exemple active le rendu avec les motifs de fond à $0000 et les sprites à $1000. Cette leçon ne vérifie pas la synchronisation sur une ligne exacte du matériel réel.

L’acquittement et la désactivation MMC3 écrivent tous deux dans $E000. Cela efface la demande actuelle et désactive les suivantes. Rappelez __mapper_irq_enable() au moment voulu pour armer une autre interruption. Aucune de ces opérations ne modifie le masque d’interruptions CPU ni l’activation NMI.

Le compilateur émet RTI pour un gestionnaire d’interruption personnalisé, mais celui-ci doit préserver les registres A/X/Y et les zones temporaires partagées qu’il modifie. L’exemple sauvegarde A/X/Y et n’utilise qu’un incrément d’octet et un acquittement sans argument. Si vous ajoutez des appels C, examinez aussi leur stockage partagé.

Exemple d’utilisation

c
__irq_disable(); __mapper_irq_disable();

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple MMC3 affiche les marqueurs CHR 19/20, 21/22 et 23/24. Les lectures CIRAM donnent A B A B en mirroring vertical et A A B B en horizontal. Les comptes IRQ sont 01→02→03→03 : chacune des trois premières activations explicites autorise une interruption ; le compteur reste fixe après désactivation. Chaque étape attend six images.

__mapper_irq_disable: MMC3-DEFAULT
MMC3-DEFAULT

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:330

Notes de déclaration (texte source)
Write MMC3 E000 to disable and acknowledge mapper IRQs.
__mapper_irq_enable

Écrit dans $E001 pour permettre à MMC3 de déclencher une IRQ lorsque la condition du compteur est remplie. Ne règle ni le verrou ni une demande de rechargement.

Syntaxe

c
void __mapper_irq_enable(void);

Valeur renvoyée

Aucune valeur de retour.

Conditions et limites

Les déclarations des intrinsèques sont dans intrinsics.h. Incluez nes_game.h pour les macros nes_mapper_irq_*. Choisissez la carte à la compilation avec --mapper.

MMC3 uniquement. Fournissez un gestionnaire __nes_irq, des transitions PPU A12 admissibles, l’activation IRQ du mapper et __irq_enable() côté CPU. L’exemple active le rendu avec les motifs de fond à $0000 et les sprites à $1000. Cette leçon ne vérifie pas la synchronisation sur une ligne exacte du matériel réel.

L’acquittement et la désactivation MMC3 écrivent tous deux dans $E000. Cela efface la demande actuelle et désactive les suivantes. Rappelez __mapper_irq_enable() au moment voulu pour armer une autre interruption. Aucune de ces opérations ne modifie le masque d’interruptions CPU ni l’activation NMI.

Le compilateur émet RTI pour un gestionnaire d’interruption personnalisé, mais celui-ci doit préserver les registres A/X/Y et les zones temporaires partagées qu’il modifie. L’exemple sauvegarde A/X/Y et n’utilise qu’un incrément d’octet et un acquittement sans argument. Si vous ajoutez des appels C, examinez aussi leur stockage partagé.

Exemple d’utilisation

c
__mapper_irq_set(37); __mapper_irq_enable(); __irq_enable();
    for (i=0;i<6;i++) { m_wait(); }
    mapper_result[15]=irq_count;

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple MMC3 affiche les marqueurs CHR 19/20, 21/22 et 23/24. Les lectures CIRAM donnent A B A B en mirroring vertical et A A B B en horizontal. Les comptes IRQ sont 01→02→03→03 : chacune des trois premières activations explicites autorise une interruption ; le compteur reste fixe après désactivation. Chaque étape attend six images.

__mapper_irq_enable: MMC3-DEFAULT
MMC3-DEFAULT

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:328

Notes de déclaration (texte source)
Write MMC3 E001. The CPU IRQ mask and a valid handler are separate caller responsibilities.
__mapper_irq_set

Écrit le verrou IRQ MMC3 à $C000 et demande son prochain rechargement à $C001. L’activation des interruptions est une opération distincte.

Syntaxe

c
void __mapper_irq_set(u8 scanline);

Paramètres

scanline

Valeur de verrouillage du compteur sur huit bits, de 0 à 255, et non une coordonnée Y absolue. Le moment du rechargement et les transitions PPU A12 admissibles déterminent le déclenchement. Zéro est valide et ne signifie pas « désactiver les interruptions ».

Valeur renvoyée

Aucune valeur de retour.

Conditions et limites

Les déclarations des intrinsèques sont dans intrinsics.h. Incluez nes_game.h pour les macros nes_mapper_irq_*. Choisissez la carte à la compilation avec --mapper.

MMC3 uniquement. Fournissez un gestionnaire __nes_irq, des transitions PPU A12 admissibles, l’activation IRQ du mapper et __irq_enable() côté CPU. L’exemple active le rendu avec les motifs de fond à $0000 et les sprites à $1000. Cette leçon ne vérifie pas la synchronisation sur une ligne exacte du matériel réel.

L’acquittement et la désactivation MMC3 écrivent tous deux dans $E000. Cela efface la demande actuelle et désactive les suivantes. Rappelez __mapper_irq_enable() au moment voulu pour armer une autre interruption. Aucune de ces opérations ne modifie le masque d’interruptions CPU ni l’activation NMI.

Le compilateur émet RTI pour un gestionnaire d’interruption personnalisé, mais celui-ci doit préserver les registres A/X/Y et les zones temporaires partagées qu’il modifie. L’exemple sauvegarde A/X/Y et n’utilise qu’un incrément d’octet et un acquittement sans argument. Si vous ajoutez des appels C, examinez aussi leur stockage partagé.

Exemple d’utilisation

c
__mapper_irq_set(37); __mapper_irq_enable(); __irq_enable();
    for (i=0;i<6;i++) { m_wait(); }
    mapper_result[15]=irq_count;

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple MMC3 affiche les marqueurs CHR 19/20, 21/22 et 23/24. Les lectures CIRAM donnent A B A B en mirroring vertical et A A B B en horizontal. Les comptes IRQ sont 01→02→03→03 : chacune des trois premières activations explicites autorise une interruption ; le compteur reste fixe après désactivation. Chaque étape attend six images.

__mapper_irq_set: MMC3-DEFAULT
MMC3-DEFAULT

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:326

Notes de déclaration (texte source)
Alias the MMC3 IRQ latch/reload operation; other mapper profiles are rejected.
__memcpy

Copie de src vers dst le nombre d’octets indiqué. Le compteur sur 16 bits permet de dépasser 255 octets, par exemple pour copier une table entière en RAM. La lecture et l’écriture suivent l’ordre croissant des adresses.

Syntaxe

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

Paramètres

dst

Pointeur vers le premier octet accessible en écriture de la destination. Prévoyez une zone contiguë assez grande pour toute l’opération ; aucun tampon n’est alloué automatiquement.

src

Pointeur vers le premier octet lisible de la source. Toute la zone doit rester accessible et inchangée pendant la copie.

len

Nombre d’octets en u16 (0–65535), et non nombre d’éléments. Le maximum du type ne garantit pas que la machine dispose d’un tampon valide de cette taille. Toute la zone doit tenir dans le mappage mémoire sélectionné.

Valeur renvoyée

Aucune (void).

Conditions et limites

Un compteur nul ne transfère aucun octet. Les effets de bord des expressions passées en argument ont néanmoins lieu : l’exemple fournit deux fonctions renvoyant des pointeurs et constate leurs deux appels malgré une longueur nulle.

Utilisez des zones source et destination sans chevauchement. Ces routines copient vers les adresses croissantes et n’offrent pas la sécurité de memmove en cas de recouvrement ; écraser une partie de la source encore non lue peut altérer la suite de la copie.

L’opération se termine avant de rendre la main. Elle ne change pas de banque ROM/RAM et ne limite pas automatiquement la zone aux bornes d’un tampon. Maintenez le mappage stable, utilisez des zones valides et coordonnez l’accès avec les interruptions utilisant les mêmes tampons.

Sur FC, dst est une adresse CPU, pas une adresse PPU. Une copie en RAM CPU ne modifie ni CHR, ni les tables de noms, ni les palettes ; leur chargement nécessite les API PPU/VRAM. Les routines auxiliaires générées partagent les arguments et les temporaires : ne les rappelez pas depuis une NMI/IRQ pendant une opération en cours.

Exemple d’utilisation

c
memory_reset();
__memcpy(memory_destination+1,memory_source+1,257);
memory_copy_word=memory_check(257,0); // 257 copied bytes; guards unchanged.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Cet exemple utilise __memcpy pour copier 257 octets suivant un motif connu à partir du décalage 1. memory_check compare les tableaux source et destination en entier ; la ligne COPY WORD doit afficher 257. Le premier octet sert de garde et les octets suivant le bloc doivent également rester inchangés.

Le programme complet initialise deux tampons de 264 octets avant chaque opération, puis vérifie tous les octets de la source, de la destination et des zones de garde. L’écran attendu affiche COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 et FAILED 000. WORD/BYTE désignent la largeur du compteur, pas la taille des éléments copiés. Ces nombres proviennent des vérifications de l’exemple ; ces fonctions void ne renvoient aucune valeur.

Les captures montrent du texte noir sur blanc sur DMG, bleu sur blanc sur CGB et bleu sur noir sur FC. L’exemple vérifie des opérations en RAM ; le texte en présente les résultats, sans illustrer une primitive de dessin. Des contrôles distincts sur émulateur couvrent 88 cas par plateforme : longueurs constantes ou déterminées à l’exécution de 0, 1, 16, 17, 32, 33, 64, 65 et 255 octets, plus 256 et 257 pour les compteurs sur 16 bits. Aucun test sur matériel réel n’a été effectué.

__memcpy: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:19

Notes de déclaration (texte source)
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

csharp
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

Copie un bloc d’au plus 255 octets avec un compteur sur 8 bits. Choisissez cette forme lorsque la longueur tient dans un octet. _small désigne la largeur du compteur ; ce nom ne garantit ni une durée d’exécution ni une taille de code précises.

Syntaxe

c
void __memcpy_small(u8* dst, const u8* src, u8 len);

Paramètres

dst

Pointeur vers le premier octet accessible en écriture de la destination. Prévoyez une zone contiguë assez grande pour toute l’opération ; aucun tampon n’est alloué automatiquement.

src

Pointeur vers le premier octet lisible de la source. Toute la zone doit rester accessible et inchangée pendant la copie.

len

Nombre d’octets en u8 : 0–255. Ne passez pas 256 pour désigner un bloc entier de 256 octets : cette longueur ne tient pas dans le paramètre. À partir de 256 octets, utilisez la fonction correspondante sans _small.

Valeur renvoyée

Aucune (void).

Conditions et limites

Un compteur nul ne transfère aucun octet. Les effets de bord des expressions passées en argument ont néanmoins lieu : l’exemple fournit deux fonctions renvoyant des pointeurs et constate leurs deux appels malgré une longueur nulle.

Utilisez des zones source et destination sans chevauchement. Ces routines copient vers les adresses croissantes et n’offrent pas la sécurité de memmove en cas de recouvrement ; écraser une partie de la source encore non lue peut altérer la suite de la copie.

L’opération se termine avant de rendre la main. Elle ne change pas de banque ROM/RAM et ne limite pas automatiquement la zone aux bornes d’un tampon. Maintenez le mappage stable, utilisez des zones valides et coordonnez l’accès avec les interruptions utilisant les mêmes tampons.

Sur FC, dst est une adresse CPU, pas une adresse PPU. Une copie en RAM CPU ne modifie ni CHR, ni les tables de noms, ni les palettes ; leur chargement nécessite les API PPU/VRAM. Les routines auxiliaires générées partagent les arguments et les temporaires : ne les rappelez pas depuis une NMI/IRQ pendant une opération en cours.

Exemple d’utilisation

c
memory_reset();
__memcpy_small(memory_destination+1,memory_source+1,255);
memory_copy_byte=memory_check(255,0); // Maximum representable byte count.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Cet exemple utilise __memcpy_small pour copier 255 octets suivant un motif connu à partir du décalage 1. memory_check compare les tableaux source et destination en entier ; la ligne COPY BYTE doit afficher 255. Le premier octet sert de garde et les octets suivant le bloc doivent également rester inchangés.

Le programme complet initialise deux tampons de 264 octets avant chaque opération, puis vérifie tous les octets de la source, de la destination et des zones de garde. L’écran attendu affiche COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 et FAILED 000. WORD/BYTE désignent la largeur du compteur, pas la taille des éléments copiés. Ces nombres proviennent des vérifications de l’exemple ; ces fonctions void ne renvoient aucune valeur.

Les captures montrent du texte noir sur blanc sur DMG, bleu sur blanc sur CGB et bleu sur noir sur FC. L’exemple vérifie des opérations en RAM ; le texte en présente les résultats, sans illustrer une primitive de dessin. Des contrôles distincts sur émulateur couvrent 88 cas par plateforme : longueurs constantes ou déterminées à l’exécution de 0, 1, 16, 17, 32, 33, 64, 65 et 255 octets, plus 256 et 257 pour les compteurs sur 16 bits. Aucun test sur matériel réel n’a été effectué.

__memcpy_small: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:23

Notes de déclaration (texte source)
Forward copy with an 8-bit length, zero through 255; the dynamic helper promotes it to a word count.

kitaqfc/kitaqfc/CodeGenerator.cs:4610

csharp
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

Remplit la zone de destination avec une même valeur d’un octet. Le compteur sur 16 bits convient à l’effacement d’un grand tampon ou à l’initialisation d’une table. Par exemple, 0x5A écrit 5A 5A 5A…, et non un entier de plusieurs octets répété.

Syntaxe

c
void __memset(u8* dst, u8 value, u16 len);

Paramètres

dst

Pointeur vers le premier octet accessible en écriture de la destination. Prévoyez une zone contiguë assez grande pour toute l’opération ; aucun tampon n’est alloué automatiquement.

value

Octet répété à chaque adresse de destination : 0–255. La valeur 0 remet la zone à zéro. Pour initialiser un tableau de u16 avec un nombre non nul, affectez ce nombre à chaque élément.

len

Nombre d’octets en u16 (0–65535), et non nombre d’éléments. Le maximum du type ne garantit pas que la machine dispose d’un tampon valide de cette taille. Toute la zone doit tenir dans le mappage mémoire sélectionné.

Valeur renvoyée

Aucune (void).

Conditions et limites

Un compteur nul ne transfère aucun octet. Les effets de bord des expressions passées en argument ont néanmoins lieu : l’exemple fournit deux fonctions renvoyant des pointeurs et constate leurs deux appels malgré une longueur nulle.

L’opération se termine avant de rendre la main. Elle ne change pas de banque ROM/RAM et ne limite pas automatiquement la zone aux bornes d’un tampon. Maintenez le mappage stable, utilisez des zones valides et coordonnez l’accès avec les interruptions utilisant les mêmes tampons.

Sur FC, dst est une adresse CPU, pas une adresse PPU. Une copie en RAM CPU ne modifie ni CHR, ni les tables de noms, ni les palettes ; leur chargement nécessite les API PPU/VRAM. Les routines auxiliaires générées partagent les arguments et les temporaires : ne les rappelez pas depuis une NMI/IRQ pendant une opération en cours.

Exemple d’utilisation

c
memory_reset();
__memset(memory_destination+1,0x5A,257);
memory_fill_word=memory_check(257,1); // Repeat one byte, not a 16-bit word.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Cet exemple utilise __memset pour remplir 257 octets avec 0x5A à partir du décalage 1. memory_check exige que tous ces octets valent 0x5A, que les octets de garde restent à 0xCC et que le tampon source distinct ne change pas. La ligne FILL WORD doit afficher 257.

Le programme complet initialise deux tampons de 264 octets avant chaque opération, puis vérifie tous les octets de la source, de la destination et des zones de garde. L’écran attendu affiche COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 et FAILED 000. WORD/BYTE désignent la largeur du compteur, pas la taille des éléments copiés. Ces nombres proviennent des vérifications de l’exemple ; ces fonctions void ne renvoient aucune valeur.

Les captures montrent du texte noir sur blanc sur DMG, bleu sur blanc sur CGB et bleu sur noir sur FC. L’exemple vérifie des opérations en RAM ; le texte en présente les résultats, sans illustrer une primitive de dessin. Des contrôles distincts sur émulateur couvrent 88 cas par plateforme : longueurs constantes ou déterminées à l’exécution de 0, 1, 16, 17, 32, 33, 64, 65 et 255 octets, plus 256 et 257 pour les compteurs sur 16 bits. Aucun test sur matériel réel n’a été effectué.

__memset: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:21

Notes de déclaration (texte source)
Fill len CPU bytes with value. Zero length still evaluates argument side effects.

kitaqfc/kitaqfc/CodeGenerator.cs:4613

csharp
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

Remplit jusqu’à 255 octets consécutifs avec une même valeur, à l’aide d’un compteur sur 8 bits. Cette forme convient à l’initialisation d’un petit tampon de travail. Au-delà de 255 octets, utilisez __memset.

Syntaxe

c
void __memset_small(u8* dst, u8 value, u8 len);

Paramètres

dst

Pointeur vers le premier octet accessible en écriture de la destination. Prévoyez une zone contiguë assez grande pour toute l’opération ; aucun tampon n’est alloué automatiquement.

value

Octet répété à chaque adresse de destination : 0–255. La valeur 0 remet la zone à zéro. Pour initialiser un tableau de u16 avec un nombre non nul, affectez ce nombre à chaque élément.

len

Nombre d’octets en u8 : 0–255. Ne passez pas 256 pour désigner un bloc entier de 256 octets : cette longueur ne tient pas dans le paramètre. À partir de 256 octets, utilisez la fonction correspondante sans _small.

Valeur renvoyée

Aucune (void).

Conditions et limites

Un compteur nul ne transfère aucun octet. Les effets de bord des expressions passées en argument ont néanmoins lieu : l’exemple fournit deux fonctions renvoyant des pointeurs et constate leurs deux appels malgré une longueur nulle.

L’opération se termine avant de rendre la main. Elle ne change pas de banque ROM/RAM et ne limite pas automatiquement la zone aux bornes d’un tampon. Maintenez le mappage stable, utilisez des zones valides et coordonnez l’accès avec les interruptions utilisant les mêmes tampons.

Sur FC, dst est une adresse CPU, pas une adresse PPU. Une copie en RAM CPU ne modifie ni CHR, ni les tables de noms, ni les palettes ; leur chargement nécessite les API PPU/VRAM. Les routines auxiliaires générées partagent les arguments et les temporaires : ne les rappelez pas depuis une NMI/IRQ pendant une opération en cours.

Exemple d’utilisation

c
memory_reset();
__memset_small(memory_destination+1,0x5A,255);
memory_fill_byte=memory_check(255,1);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Cet exemple utilise __memset_small pour remplir 255 octets avec 0x5A à partir du décalage 1. memory_check exige que tous ces octets valent 0x5A, que les octets de garde restent à 0xCC et que le tampon source distinct ne change pas. La ligne FILL BYTE doit afficher 255.

Le programme complet initialise deux tampons de 264 octets avant chaque opération, puis vérifie tous les octets de la source, de la destination et des zones de garde. L’écran attendu affiche COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 et FAILED 000. WORD/BYTE désignent la largeur du compteur, pas la taille des éléments copiés. Ces nombres proviennent des vérifications de l’exemple ; ces fonctions void ne renvoient aucune valeur.

Les captures montrent du texte noir sur blanc sur DMG, bleu sur blanc sur CGB et bleu sur noir sur FC. L’exemple vérifie des opérations en RAM ; le texte en présente les résultats, sans illustrer une primitive de dessin. Des contrôles distincts sur émulateur couvrent 88 cas par plateforme : longueurs constantes ou déterminées à l’exécution de 0, 1, 16, 17, 32, 33, 64, 65 et 255 octets, plus 256 et 257 pour les compteurs sur 16 bits. Aucun test sur matériel réel n’a été effectué.

__memset_small: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:25

Notes de déclaration (texte source)
Fill zero through 255 CPU bytes; this is ordinary RAM access, not a PPU transfer.

kitaqfc/kitaqfc/CodeGenerator.cs:4616

csharp
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

Déploie un flux de parties de sprite muni d’un terminateur dans des entrées consécutives du tampon miroir de la page 02. Chaque partie ajoute ses décalages X/Y d’un octet à la position de base et fournit sa tuile et ses attributs. Renvoie l’indice OAM suivant.

Syntaxe

c
u8 __metasprite_draw(u8 oam_index, u8 base_x, u8 base_y, const u8* metasprite);

Paramètres

oam_index

Indice du sprite, de 0 à 63, et non décalage en octets. Le calcul multiplie cet indice par quatre dans un curseur de 8 bits ; les valeurs hors plage sont ramenées modulo 64 et peuvent écraser une autre entrée. L’allocation n’est pas vérifiée.

base_x

Position horizontale en pixels, sous forme d’octet non signé ; ce n’est pas une coordonnée de tuile.

base_y

Octet Y de l’OAM. Pour placer le haut sous la ligne zéro, passez sa position moins un ; 239..255 sont hors des 240 lignes visibles.

metasprite

Pointeur vers des enregistrements lisibles de quatre octets : dx, dy, tile, attr. Un 0xFF à la position dx suivante termine le flux ; aucun octet supplémentaire n’est nécessaire. Les décalages s’ajoutent modulo 256 : 0xFE représente −2 et dy=0xFF peut représenter −1, mais dx=0xFF est réservé au terminateur. Le pointeur doit être valide dans la mémoire actuellement accessible.

Valeur renvoyée

Indice OAM suivant : (oam_index + parties_écrites) modulo 64. Il s’agit d’un curseur, pas du nombre de parties dessinées. Un flux vide renvoie oam_index modulo 64.

Conditions et limites

Ces opérations modifient la RAM CPU 0x0200–0x02FF, sans mettre immédiatement à jour l’OAM matérielle. Transférez la page complète avec __oam_dma. Elles ne tiennent à jour ni les indicateurs d’allocation, ni les tableaux de coordonnées, ni le compteur de la bibliothèque de sprites. Évitez qu’une interruption modifie ou transfère une page encore en préparation.

Le nombre de parties, le découpage à l’écran et la capacité ne sont pas vérifiés. Ajoutez un terminateur et limitez-vous à 64−oam_index parties. Les coordonnées sont ramenées modulo 256 ; après l’entrée 63, l’écriture reprend à l’entrée 0. Un flux vide ne modifie rien. Ce format diffère du tableau MetaSpritePart à nombre explicite utilisé par metasprite_draw dans la bibliothèque de sprites.

Exemple d’utilisation

c
fc_oam_next=__metasprite_draw(5,136,95,fc_oam_pair); // Returns 7 after two entries.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple réserve une ligne légendée à chaque opération. SET et MOVE montrent des flèches de 8×8 vers la droite en (144,16) et (144,32) ; TILE, un carré vert en (144,48) ; ATTR, la flèche retournée selon les deux axes en (144,64). HIDE en (144,80) et CLEAR en (144,112) doivent rester vides. META montre une paire en (136,96) et (144,96), la seconde flèche étant retournée horizontalement. NEXT 07 indique l’indice suivant après les entrées 5 et 6. Les bords sont rouges, la tige verte et les détails de la pointe bleus. La vérification compare 8 704 pixels par image et les octets OAM transférés ; le matériel physique n’a pas été testé.

TRANSFER montre une flèche en (144,128). Ce programme envoie directement la page 02 avec __oam_dma, y compris tous les champs préparés par les opérations individuelles sur les sprites.

__metasprite_draw: PAGE02
PAGE02

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:203

Notes de déclaration (texte source)
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.
csharp
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

Lit le signal numérique du microphone de la seconde manette Famicom dans le bit 2 de $4016. La routine lit le registre une fois, applique le masque 0x04 et normalise à 0 ou 1 sans inversion. Elle ne verrouille pas les états des manettes, n’enregistre pas de son, ne mesure pas le volume et ne reconnaît pas la parole.

Syntaxe

c
u8 __mic_read2p(void);

Valeur renvoyée

Exactement 1 si $4016 & 0x04 est non nul, sinon 0. Il s’agit du niveau numérique échantillonné, pas d’un masque de boutons, d’une amplitude, d’une fréquence ou d’un nombre de mots prononcés.

Conditions et limites

Bien que le microphone appartienne à la manette 2, cette routine accède à $4016, pas à $4017. Une lecture du registre peut aussi faire avancer le flux série de la manette lorsque le strobe est bas ; n’insérez donc pas de lecture du microphone dans une autre boucle de lecture de huit bits. Aucun historique d’entrée mémorisé n’est mis à jour.

Le modèle de contrôleurs du KUROSAKI publié ne renvoie que D0 et le bit 6 fixe. D1 et le bit du microphone valent donc toujours 0 dans cet émulateur, même lorsqu’un bouton ordinaire est maintenu. L’exécution fournie vérifie la compilation, l’entrée normale et cette observation de signal nul. Les signaux périphériques non nuls, les délais électriques et la compatibilité matérielle restent non vérifiés ; un zéro ici ne prouve ni le bon fonctionnement du périphérique ni l’impossibilité de le lire sur une machine réelle.

Exemple d’utilisation

c
mic_direct=__mic_read2p(); // A separate sample of the same digital signal.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Ce programme compare l’entrée ordinaire D0, l’extension D1 et le niveau numérique du microphone. Maintenez A sur la manette 1 et B sur la manette 2 jusqu’à la capture. NORMAL P1 et NORMAL P2 affichent 001 et 002. Dans l’exécution KUROSAKI fournie, D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL et MIC DIRECT affichent 000, car ces lignes ne sont pas émulées. Les valeurs restent ensuite fixes. Avec des périphériques adaptés et leurs lignes implémentées, les valeurs dépendent des entrées réelles ; interprétez-les selon les bits décrits ci-dessus au lieu d’attendre ces zéros.

Le modèle de contrôleurs du KUROSAKI publié ne renvoie que D0 et le bit 6 fixe. D1 et le bit du microphone valent donc toujours 0 dans cet émulateur, même lorsqu’un bouton ordinaire est maintenu. L’exécution fournie vérifie la compilation, l’entrée normale et cette observation de signal nul. Les signaux périphériques non nuls, les délais électriques et la compatibilité matérielle restent non vérifiés ; un zéro ici ne prouve ni le bon fonctionnement du périphérique ni l’impossibilité de le lire sur une machine réelle.

__mic_read2p: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:233

Notes de déclaration (texte source)
Alias the same normalized microphone input bit as __joypad2p_voice.
__midi_clock

Envoie un octet d’horloge F8. L’appelant programme les impulsions répétées.

Syntaxe

c
void __midi_clock(void);

Valeur renvoyée

Aucune valeur de retour. Modifie les registres audio ou l’état du pilote.

Conditions et limites

Nécessite un circuit externe reliant $4016 D0 à TX et $4017 D4 à RX. Une manette standard ne fournit pas MIDI, et ces appels ne jouent pas l’APU interne. Arrêtez DMC/DMA et masquez NMI et IRQ pendant les transferts ; évitez les accès concurrents aux ports et à la zone de travail partagée. Les 57 cycles par bit NTSC donnent environ 31 400 bit/s, soit +0,5 % par rapport à 31 250. Le minutage PAL et les échanges avec un adaptateur physique ne sont pas vérifiés.

Exemple d’utilisation

c
// 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();
}

Résultat attendu

L’écran affiche EXTERNAL PORT DATA et aucune voix interne ne sonne. Les octets transmis en hexadécimal sont 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Le contrôle exige des cellules de transmission NTSC de 57 cycles et des lectures espacées de 57 cycles. Le 00 reçu vient de l’entrée non connectée de l’émulateur et ne prouve pas une communication externe.

__midi_clock: NTSC
NTSC

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:304

Notes de déclaration (texte source)
Transmit the single realtime byte F8 synchronously; this does not schedule future ticks.

kitaqfc/kitaqfc/CodeGenerator.cs:4803

csharp
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

Envoie un octet temps réel Continue FB sans agir sur le pilote musical interne.

Syntaxe

c
void __midi_continue(void);

Valeur renvoyée

Aucune valeur de retour. Modifie les registres audio ou l’état du pilote.

Conditions et limites

Nécessite un circuit externe reliant $4016 D0 à TX et $4017 D4 à RX. Une manette standard ne fournit pas MIDI, et ces appels ne jouent pas l’APU interne. Arrêtez DMC/DMA et masquez NMI et IRQ pendant les transferts ; évitez les accès concurrents aux ports et à la zone de travail partagée. Les 57 cycles par bit NTSC donnent environ 31 400 bit/s, soit +0,5 % par rapport à 31 250. Le minutage PAL et les échanges avec un adaptateur physique ne sont pas vérifiés.

Exemple d’utilisation

c
// 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();
}

Résultat attendu

L’écran affiche EXTERNAL PORT DATA et aucune voix interne ne sonne. Les octets transmis en hexadécimal sont 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Le contrôle exige des cellules de transmission NTSC de 57 cycles et des lectures espacées de 57 cycles. Le 00 reçu vient de l’entrée non connectée de l’émulateur et ne prouve pas une communication externe.

__midi_continue: NTSC
NTSC

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:308

Notes de déclaration (texte source)
Transmit realtime Continue (FB) through the serial adapter path.

kitaqfc/kitaqfc/CodeGenerator.cs:4805

csharp
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

Envoie un changement de contrôleur : 0xB0 | (ch & 0x0F), numéro de contrôleur, valeur.

Syntaxe

c
void __midi_control_change(u8 ch, u8 cc, u8 value);

Paramètres

ch

Canal MIDI de 0 à 15 ; le demi-octet bas entre dans l’octet d’état. Zéro correspond au canal MIDI affiché 1.

cc

Numéro de contrôleur de 0 à 127 ; 7 correspond au volume du canal.

value

Valeur de contrôleur de 0 à 127 ; les bits supérieurs ne sont pas effacés automatiquement.

Valeur renvoyée

Aucune valeur de retour. Modifie les registres audio ou l’état du pilote.

Conditions et limites

Nécessite un circuit externe reliant $4016 D0 à TX et $4017 D4 à RX. Une manette standard ne fournit pas MIDI, et ces appels ne jouent pas l’APU interne. Arrêtez DMC/DMA et masquez NMI et IRQ pendant les transferts ; évitez les accès concurrents aux ports et à la zone de travail partagée. Les 57 cycles par bit NTSC donnent environ 31 400 bit/s, soit +0,5 % par rapport à 31 250. Le minutage PAL et les échanges avec un adaptateur physique ne sont pas vérifiés.

Exemple d’utilisation

c
// 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();
}

Résultat attendu

L’écran affiche EXTERNAL PORT DATA et aucune voix interne ne sonne. Les octets transmis en hexadécimal sont 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Le contrôle exige des cellules de transmission NTSC de 57 cycles et des lectures espacées de 57 cycles. Le 00 reçu vient de l’entrée non connectée de l’émulateur et ne prouve pas une communication externe.

__midi_control_change: NTSC
NTSC

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:300

Notes de déclaration (texte source)
Send B0|(ch&0F), controller and value; both data arguments must be valid 7-bit values.

kitaqfc/kitaqfc/CodeGenerator.cs:4800

csharp
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

Attend une entrée de réception basse, puis échantillonne huit bits à intervalles temporisés.

Syntaxe

c
u8 __midi_in_byte(void);

Valeur renvoyée

Renvoie le u8 reçu. Aucun délai d’expiration, contrôle de trame ni tampon de réception. Une entrée maintenue haute bloque indéfiniment ; maintenue basse, elle donne zéro.

Conditions et limites

Nécessite un circuit externe reliant $4016 D0 à TX et $4017 D4 à RX. Une manette standard ne fournit pas MIDI, et ces appels ne jouent pas l’APU interne. Arrêtez DMC/DMA et masquez NMI et IRQ pendant les transferts ; évitez les accès concurrents aux ports et à la zone de travail partagée. Les 57 cycles par bit NTSC donnent environ 31 400 bit/s, soit +0,5 % par rapport à 31 250. Le minutage PAL et les échanges avec un adaptateur physique ne sont pas vérifiés.

Exemple d’utilisation

c
// 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();
}

Résultat attendu

L’écran affiche EXTERNAL PORT DATA et aucune voix interne ne sonne. Les octets transmis en hexadécimal sont 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Le contrôle exige des cellules de transmission NTSC de 57 cycles et des lectures espacées de 57 cycles. Le 00 reçu vient de l’entrée non connectée de l’émulateur et ne prouve pas une communication externe.

__midi_in_byte: NTSC
NTSC

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:294

Notes de déclaration (texte source)
Block for a low start signal and sample eight bits. No timeout, framing check or receive buffer is provided.

kitaqfc/kitaqfc/CodeGenerator.cs:4796

csharp
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

Envoie note-off en trois octets : 0x80 | (ch & 0x0F), note, velocity.

Syntaxe

c
void __midi_note_off(u8 ch, u8 note, u8 velocity);

Paramètres

ch

Canal MIDI de 0 à 15 ; le demi-octet bas entre dans l’octet d’état. Zéro correspond au canal MIDI affiché 1.

note

Note MIDI de 0 à 127 ; 69 est A4. Aucun masque sur sept bits n’est appliqué : l’appelant respecte la plage.

velocity

Vélocité de 0 à 127 ; aucun masquage automatique sur sept bits.

Valeur renvoyée

Aucune valeur de retour. Modifie les registres audio ou l’état du pilote.

Conditions et limites

Nécessite un circuit externe reliant $4016 D0 à TX et $4017 D4 à RX. Une manette standard ne fournit pas MIDI, et ces appels ne jouent pas l’APU interne. Arrêtez DMC/DMA et masquez NMI et IRQ pendant les transferts ; évitez les accès concurrents aux ports et à la zone de travail partagée. Les 57 cycles par bit NTSC donnent environ 31 400 bit/s, soit +0,5 % par rapport à 31 250. Le minutage PAL et les échanges avec un adaptateur physique ne sont pas vérifiés.

Exemple d’utilisation

c
// 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();
}

Résultat attendu

L’écran affiche EXTERNAL PORT DATA et aucune voix interne ne sonne. Les octets transmis en hexadécimal sont 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Le contrôle exige des cellules de transmission NTSC de 57 cycles et des lectures espacées de 57 cycles. Le 00 reçu vient de l’entrée non connectée de l’émulateur et ne prouve pas une communication externe.

__midi_note_off: NTSC
NTSC

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:298

Notes de déclaration (texte source)
Send 80|(ch&0F), note and velocity through the same synchronous serial path.

kitaqfc/kitaqfc/CodeGenerator.cs:4799

csharp
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

Envoie note-on en trois octets : 0x90 | (ch & 0x0F), note, velocity.

Syntaxe

c
void __midi_note_on(u8 ch, u8 note, u8 velocity);

Paramètres

ch

Canal MIDI de 0 à 15 ; le demi-octet bas entre dans l’octet d’état. Zéro correspond au canal MIDI affiché 1.

note

Note MIDI de 0 à 127 ; 69 est A4. Aucun masque sur sept bits n’est appliqué : l’appelant respecte la plage.

velocity

Vélocité de 0 à 127 ; aucun masquage automatique sur sept bits.

Valeur renvoyée

Aucune valeur de retour. Modifie les registres audio ou l’état du pilote.

Conditions et limites

Nécessite un circuit externe reliant $4016 D0 à TX et $4017 D4 à RX. Une manette standard ne fournit pas MIDI, et ces appels ne jouent pas l’APU interne. Arrêtez DMC/DMA et masquez NMI et IRQ pendant les transferts ; évitez les accès concurrents aux ports et à la zone de travail partagée. Les 57 cycles par bit NTSC donnent environ 31 400 bit/s, soit +0,5 % par rapport à 31 250. Le minutage PAL et les échanges avec un adaptateur physique ne sont pas vérifiés.

Exemple d’utilisation

c
// 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();
}

Résultat attendu

L’écran affiche EXTERNAL PORT DATA et aucune voix interne ne sonne. Les octets transmis en hexadécimal sont 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Le contrôle exige des cellules de transmission NTSC de 57 cycles et des lectures espacées de 57 cycles. Le 00 reçu vient de l’entrée non connectée de l’émulateur et ne prouve pas une communication externe.

__midi_note_on: NTSC
NTSC

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:296

Notes de déclaration (texte source)
Send 90|(ch&0F), note and velocity. Supply 7-bit data bytes; they are not masked by this helper.

kitaqfc/kitaqfc/CodeGenerator.cs:4798

csharp
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

Transmet synchronement un octet 8N1 : début bas, huit bits de données du moins au plus significatif, arrêt haut.

Syntaxe

c
void __midi_out_byte(u8 byte);

Paramètres

byte

Octet brut de 0 à 255, qu’il s’agisse d’un état ou de données.

Valeur renvoyée

Aucune valeur de retour. Modifie les registres audio ou l’état du pilote.

Conditions et limites

Nécessite un circuit externe reliant $4016 D0 à TX et $4017 D4 à RX. Une manette standard ne fournit pas MIDI, et ces appels ne jouent pas l’APU interne. Arrêtez DMC/DMA et masquez NMI et IRQ pendant les transferts ; évitez les accès concurrents aux ports et à la zone de travail partagée. Les 57 cycles par bit NTSC donnent environ 31 400 bit/s, soit +0,5 % par rapport à 31 250. Le minutage PAL et les échanges avec un adaptateur physique ne sont pas vérifiés.

Exemple d’utilisation

c
// 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();
}

Résultat attendu

L’écran affiche EXTERNAL PORT DATA et aucune voix interne ne sonne. Les octets transmis en hexadécimal sont 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Le contrôle exige des cellules de transmission NTSC de 57 cycles et des lectures espacées de 57 cycles. Le 00 reçu vient de l’entrée non connectée de l’émulateur et ne prouve pas une communication externe.

__midi_out_byte: NTSC
NTSC

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:292

Notes de déclaration (texte source)
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

csharp
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

Envoie un changement de programme : 0xC0 | (ch & 0x0F), puis le numéro de programme.

Syntaxe

c
void __midi_program_change(u8 ch, u8 program);

Paramètres

ch

Canal MIDI de 0 à 15 ; le demi-octet bas entre dans l’octet d’état. Zéro correspond au canal MIDI affiché 1.

program

Programme de 0 à 127 ; l’affectation des instruments dépend du récepteur.

Valeur renvoyée

Aucune valeur de retour. Modifie les registres audio ou l’état du pilote.

Conditions et limites

Nécessite un circuit externe reliant $4016 D0 à TX et $4017 D4 à RX. Une manette standard ne fournit pas MIDI, et ces appels ne jouent pas l’APU interne. Arrêtez DMC/DMA et masquez NMI et IRQ pendant les transferts ; évitez les accès concurrents aux ports et à la zone de travail partagée. Les 57 cycles par bit NTSC donnent environ 31 400 bit/s, soit +0,5 % par rapport à 31 250. Le minutage PAL et les échanges avec un adaptateur physique ne sont pas vérifiés.

Exemple d’utilisation

c
// 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();
}

Résultat attendu

L’écran affiche EXTERNAL PORT DATA et aucune voix interne ne sonne. Les octets transmis en hexadécimal sont 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Le contrôle exige des cellules de transmission NTSC de 57 cycles et des lectures espacées de 57 cycles. Le 00 reçu vient de l’entrée non connectée de l’émulateur et ne prouve pas une communication externe.

__midi_program_change: NTSC
NTSC

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:302

Notes de déclaration (texte source)
Send C0|(ch&0F) followed by the caller-supplied 7-bit program value.

kitaqfc/kitaqfc/CodeGenerator.cs:4801

csharp
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

Envoie un octet temps réel Start FA sans changer les pointeurs musicaux du jeu.

Syntaxe

c
void __midi_start(void);

Valeur renvoyée

Aucune valeur de retour. Modifie les registres audio ou l’état du pilote.

Conditions et limites

Nécessite un circuit externe reliant $4016 D0 à TX et $4017 D4 à RX. Une manette standard ne fournit pas MIDI, et ces appels ne jouent pas l’APU interne. Arrêtez DMC/DMA et masquez NMI et IRQ pendant les transferts ; évitez les accès concurrents aux ports et à la zone de travail partagée. Les 57 cycles par bit NTSC donnent environ 31 400 bit/s, soit +0,5 % par rapport à 31 250. Le minutage PAL et les échanges avec un adaptateur physique ne sont pas vérifiés.

Exemple d’utilisation

c
// 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();
}

Résultat attendu

L’écran affiche EXTERNAL PORT DATA et aucune voix interne ne sonne. Les octets transmis en hexadécimal sont 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Le contrôle exige des cellules de transmission NTSC de 57 cycles et des lectures espacées de 57 cycles. Le 00 reçu vient de l’entrée non connectée de l’émulateur et ne prouve pas une communication externe.

__midi_start: NTSC
NTSC

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:306

Notes de déclaration (texte source)
Transmit realtime Start (FA) through the serial adapter path.

kitaqfc/kitaqfc/CodeGenerator.cs:4804

csharp
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

Envoie un octet temps réel Stop FC. Cela ne coupe directement ni l’APU ni les notes déjà envoyées.

Syntaxe

c
void __midi_stop(void);

Valeur renvoyée

Aucune valeur de retour. Modifie les registres audio ou l’état du pilote.

Conditions et limites

Nécessite un circuit externe reliant $4016 D0 à TX et $4017 D4 à RX. Une manette standard ne fournit pas MIDI, et ces appels ne jouent pas l’APU interne. Arrêtez DMC/DMA et masquez NMI et IRQ pendant les transferts ; évitez les accès concurrents aux ports et à la zone de travail partagée. Les 57 cycles par bit NTSC donnent environ 31 400 bit/s, soit +0,5 % par rapport à 31 250. Le minutage PAL et les échanges avec un adaptateur physique ne sont pas vérifiés.

Exemple d’utilisation

c
// 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();
}

Résultat attendu

L’écran affiche EXTERNAL PORT DATA et aucune voix interne ne sonne. Les octets transmis en hexadécimal sont 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. Le contrôle exige des cellules de transmission NTSC de 57 cycles et des lectures espacées de 57 cycles. Le 00 reçu vient de l’entrée non connectée de l’émulateur et ne prouve pas une communication externe.

__midi_stop: NTSC
NTSC

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:310

Notes de déclaration (texte source)
Transmit realtime Stop (FC) through the serial adapter path.

kitaqfc/kitaqfc/CodeGenerator.cs:4806

csharp
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

Associe les quatre régions logiques de tables de noms aux deux pages de RAM écran de la console, pour organiser le défilement ou choisir une page d’écran unique.

Syntaxe

c
void __mirroring_set(u8 mode);

Paramètres

mode

Les numéros de mode varient selon la carte. MMC1/SUROM : 0=écran unique bas, 1=écran unique haut, 2=vertical, 3=horizontal. MMC3 : 0=vertical, 1=horizontal. MMC5 : 0=horizontal, 1=vertical, 2=écran unique bas, 3=écran unique haut. AxROM : 0=écran unique bas, 1=écran unique haut. FME7 : 0=vertical, 1=horizontal, 2=écran unique bas, 3=écran unique haut. MMC3/AxROM utilisent le bit de poids faible ; les autres, les deux bits de poids faible.

Valeur renvoyée

Aucune valeur de retour.

Conditions et limites

Les déclarations des intrinsèques sont dans intrinsics.h. Incluez nes_game.h pour les macros nes_mapper_irq_*. Choisissez la carte à la compilation avec --mapper.

Le mirroring vertical fait correspondre les quatre régions à A B A B ; l’horizontal donne A A B B. L’écran unique bas donne A A A A, le haut B B B B. Cela change le mappage mémoire, sans retourner ni copier les graphismes du fond. Les cartes non prises en charge, dont NROM à mirroring fixe, sont refusées à la compilation.

MMC1 écrit le contrôle $0C|mode, sélectionnant une fenêtre PRG haute fixe de 16 Kio et le mode CHR 8 Kio. SUROM met aussi à jour sa copie logicielle du contrôle. AxROM change le bit d’écran en conservant la banque PRG suivie à l’exécution. Si vous écrivez aussi directement dans les registres du mapper, maintenez leur cohérence avec l’état de banque du programme.

Pour MMC5, l’API écrit $50/$44/$00/$55 dans $5105. Ces dispositions n’utilisent que les deux pages CIRAM de la console. ExRAM et le mode de remplissage sont hors de cette API. KUROSAKI prend en charge les seize dispositions utilisant uniquement la CIRAM ; le rendu MMC5 ExRAM et de remplissage n’est pas pris en charge.

Remplacez --mapper=mmc3 dans l’exemple par mmc1, mmc5, axrom ou fme7 pour examiner chaque carte. L’exemple initialise les banques CHR de police MMC5/FME7 via leurs registres de carte.

Exemple d’utilisation

c
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++; }
    }

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

La leçon écrit 17 dans la page RAM physique basse et 34 dans la page haute, puis lit les quatre régions logiques. A désigne la page basse, B la haute. Les régions portant la même lettre partagent la RAM : une écriture dans l’une est visible dans les autres. Les colonnes sont $2000, $2400, $2800 et $2C00 ; les lignes sont les modes 0 à 3. Le texte s’affiche après la relecture.

__mirroring_set: MMC1-DEFAULT
MMC1-DEFAULT
__mirroring_set: MMC3-DEFAULT
MMC3-DEFAULT
__mirroring_set: MMC5-DEFAULT
MMC5-DEFAULT
__mirroring_set: AXROM-DEFAULT
AXROM-DEFAULT
__mirroring_set: FME7-DEFAULT
FME7-DEFAULT

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:322

Notes de déclaration (texte source)
Emit the selected mapper mirroring operation; available modes and writable control depend on the board.
__mul16x8

Calcule une multiplication non signée.

Syntaxe

c
u16 __mul16x8(u16 a, u8 b);

Paramètres

a

Mot non signé de 0 à 65535.

b

Coefficient non signé de 0 à 255.

Valeur renvoyée

Renvoie les 16 bits de poids faible de a*b.

Conditions et limites

Sur FC, les fonctions nommées q8_8/q1_7 utilisent les produits entiers bruts, sans décalage de mise à l’échelle fixe. Les fonctions Q1.7 sont des alias des opérations signées correspondantes. L’appelant gère les unités ; les résultats signés sont renvoyés comme motifs de bits u16.

Exemple d’utilisation

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

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Comparez les entrées littérales aux valeurs en RAM. Les résultats représentatifs sont affichés sur 16 bits non signés ; les négatifs utilisent le complément à deux. Le GB remet à l’échelle les produits à virgule fixe, tandis que le FC renvoie des produits entiers : des entrées identiques ne donnent donc pas nécessairement les mêmes résultats.

L’extrait stocke les valeurs suivantes sur 16 bits : result[2]=65151, result[3]=65151. Chaque calcul avec une entrée littérale correspond à son équivalent avec une entrée en RAM. L’écran affiche des valeurs représentatives ; le tableau result conserve tous les résultats.

__mul16x8: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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.chr
Implémentation et source

kitaqfc/lib/intrinsics.h:51

Notes de déclaration (texte source)
Return the low word of the unsigned product; overflow wraps without saturation.

kitaqfc/kitaqfc/CodeGenerator.cs:4647

csharp
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

Multiplie deux octets non signés et renvoie l’octet de poids fort.

Syntaxe

c
u8 __mul8x8_hi(u8 a, u8 b);

Paramètres

a

Octet non signé de 0 à 255.

b

Octet non signé de 0 à 255.

Valeur renvoyée

floor(a*b/256), dans l’intervalle 0..254.

Exemple d’utilisation

c
// Compare literal coefficients with values read from RAM.
result[0]=__mul8x8_hi(255,255);
v0=255;v1=255;
result[1]=__mul8x8_hi(v0,v1);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Comparez les entrées littérales aux valeurs en RAM. Les résultats représentatifs sont affichés sur 16 bits non signés ; les négatifs utilisent le complément à deux. Le GB remet à l’échelle les produits à virgule fixe, tandis que le FC renvoie des produits entiers : des entrées identiques ne donnent donc pas nécessairement les mêmes résultats.

L’extrait stocke les valeurs suivantes sur 16 bits : result[0]=254, result[1]=254. Chaque calcul avec une entrée littérale correspond à son équivalent avec une entrée en RAM. L’écran affiche des valeurs représentatives ; le tableau result conserve tous les résultats.

__mul8x8_hi: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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.chr
Implémentation et source

kitaqfc/lib/intrinsics.h:49

Notes de déclaration (texte source)
Return the high byte of the unsigned 8-by-8 product, equivalent to floor(a*b/256).

kitaqfc/kitaqfc/CodeGenerator.cs:4649

csharp
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

Écrit directement un octet de tuile dans la table de noms 0, à 0x2000 + y*32 + x. Réinitialise le verrou avant de définir l’adresse. N’écrit ni graphisme de tuile ni attribut de palette et ne limite pas les coordonnées.

Syntaxe

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

Paramètres

x / y

Coordonnées de tuiles, pas de pixels. Gardez x dans 0..31 et y dans 0..29 pour la carte visible de 32×30 tuiles ; chaque tuile couvre 8×8 pixels. Les arguments d’un octet ne sont pas bornés automatiquement et peuvent écrire hors de la zone voulue.

tile

Numéro de tuile 0..255 dans la table de motifs de fond choisie par PPUCTRL. Sélectionne un graphisme CHR existant, pas une couleur de pixel ni un pointeur source.

Valeur renvoyée

Aucune (void).

Conditions et limites

L’accès direct à la PPU exige de désactiver le rendu ou de terminer toute l’opération dans un intervalle VBlank sûr. Ces fonctions n’attendent pas, ne mettent rien en file, ne bloquent pas NMI et ne rétablissent pas le défilement. Empêchez une interruption de modifier le verrou ou l’adresse partagés pendant l’appel. Le bit 2 de PPUCTRL choisit un pas de destination de 1 ou 32 par octet. La PPU gère les miroirs et le bouclage des adresses ; la CHR-ROM n’est pas inscriptible. Rétablissez le défilement voulu avant le rendu.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
__nametable_put(2,12,1); // Blue solid marker at (16,96).

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__nametable_put: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:124

Notes de déclaration (texte source)
Write one tile directly at 2000+y*32+x; use x<32, y<30 for visible nametable cells.

kitaqfc/kitaqfc/CodeGenerator.cs:4820

csharp
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

Sélectionne la table logique nt & 3 et écrit une tuile à 0x2000 + (nt & 3)*0x400 + y*32 + x. Réinitialise le verrou avant la paire d’adresse. Le mirroring de la cartouche peut faire correspondre plusieurs numéros logiques à la même mémoire physique.

Syntaxe

c
void __nametable_put_nt(u8 nt, u8 x, u8 y, u8 tile);

Paramètres

nt

Sélecteur de table logique. Seuls les deux bits faibles sont utilisés (nt & 3) : 4 choisit la table 0 et 7 la table 3. N’alloue pas de mémoire indépendante et ne change pas le mirroring du mapper.

x / y

Coordonnées de tuiles, pas de pixels. Gardez x dans 0..31 et y dans 0..29 pour la carte visible de 32×30 tuiles ; chaque tuile couvre 8×8 pixels. Les arguments d’un octet ne sont pas bornés automatiquement et peuvent écrire hors de la zone voulue.

tile

Numéro de tuile 0..255 dans la table de motifs de fond choisie par PPUCTRL. Sélectionne un graphisme CHR existant, pas une couleur de pixel ni un pointeur source.

Valeur renvoyée

Aucune (void).

Conditions et limites

L’accès direct à la PPU exige de désactiver le rendu ou de terminer toute l’opération dans un intervalle VBlank sûr. Ces fonctions n’attendent pas, ne mettent rien en file, ne bloquent pas NMI et ne rétablissent pas le défilement. Empêchez une interruption de modifier le verrou ou l’adresse partagés pendant l’appel. Le bit 2 de PPUCTRL choisit un pas de destination de 1 ou 32 par octet. La PPU gère les miroirs et le bouclage des adresses ; la CHR-ROM n’est pas inscriptible. Rétablissez le défilement voulu avant le rendu.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
__nametable_put_nt(4,10,12,2); // Red left-half marker at (80,96), table 0.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__nametable_put_nt: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:126

Notes de déclaration (texte source)
Write one tile in nametable nt&3. Mirroring can alias logical nametables; coordinates are unchecked.

kitaqfc/kitaqfc/CodeGenerator.cs:4821

csharp
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

Remplit un rectangle de w par h tuiles dans la table de noms 0 à partir de (x,y). Réinitialise le verrou et l’adresse à chaque ligne, écrit w copies de tile, puis avance le début de ligne suivant de 32 octets. Ne modifie pas les attributs de palette.

Syntaxe

c
void __nametable_rect(u8 x, u8 y, u8 w, u8 h, u8 tile);

Paramètres

x / y

Coordonnées de tuiles, pas de pixels. Gardez x dans 0..31 et y dans 0..29 pour la carte visible de 32×30 tuiles ; chaque tuile couvre 8×8 pixels. Les arguments d’un octet ne sont pas bornés automatiquement et peuvent écrire hors de la zone voulue.

w / h

Largeur et hauteur en tuiles, chacune sur un octet non signé. Respectez x+w<=32 et y+h<=30. Une hauteur nulle n’accède pas à la PPU. Une largeur nulle n’écrit aucune tuile, mais réinitialise le verrou et définit les h adresses de ligne. Utilisez l’incrément PPU 1 pour les lignes horizontales.

tile

Numéro de tuile 0..255 dans la table de motifs de fond choisie par PPUCTRL. Sélectionne un graphisme CHR existant, pas une couleur de pixel ni un pointeur source.

Valeur renvoyée

Aucune (void).

Conditions et limites

L’accès direct à la PPU exige de désactiver le rendu ou de terminer toute l’opération dans un intervalle VBlank sûr. Ces fonctions n’attendent pas, ne mettent rien en file, ne bloquent pas NMI et ne rétablissent pas le défilement. Empêchez une interruption de modifier le verrou ou l’adresse partagés pendant l’appel. Le bit 2 de PPUCTRL choisit un pas de destination de 1 ou 32 par octet. La PPU gère les miroirs et le bouclage des adresses ; la CHR-ROM n’est pas inscriptible. Rétablissez le défilement voulu avant le rendu.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
__nametable_rect(2,6,8,4,1); // Solid green rectangle at (16,48), 64x32 pixels.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__nametable_rect: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:129

Notes de déclaration (texte source)
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

csharp
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

Remplit w tuiles par ligne sur h lignes dans la table logique nt & 3. La première adresse est 0x2000 + (nt & 3)*0x400 + y*32 + x ; chaque début de ligne avance de 32 octets. Écrit uniquement les tuiles, sans attributs, découpage aux limites ni configuration du mirroring.

Syntaxe

c
void __nametable_rect_nt(u8 nt, u8 x, u8 y, u8 w, u8 h, u8 tile);

Paramètres

nt

Sélecteur de table logique. Seuls les deux bits faibles sont utilisés (nt & 3) : 4 choisit la table 0 et 7 la table 3. N’alloue pas de mémoire indépendante et ne change pas le mirroring du mapper.

x / y

Coordonnées de tuiles, pas de pixels. Gardez x dans 0..31 et y dans 0..29 pour la carte visible de 32×30 tuiles ; chaque tuile couvre 8×8 pixels. Les arguments d’un octet ne sont pas bornés automatiquement et peuvent écrire hors de la zone voulue.

w / h

Largeur et hauteur en tuiles, chacune sur un octet non signé. Respectez x+w<=32 et y+h<=30. Une hauteur nulle n’accède pas à la PPU. Une largeur nulle n’écrit aucune tuile, mais réinitialise le verrou et définit les h adresses de ligne. Utilisez l’incrément PPU 1 pour les lignes horizontales.

tile

Numéro de tuile 0..255 dans la table de motifs de fond choisie par PPUCTRL. Sélectionne un graphisme CHR existant, pas une couleur de pixel ni un pointeur source.

Valeur renvoyée

Aucune (void).

Conditions et limites

L’accès direct à la PPU exige de désactiver le rendu ou de terminer toute l’opération dans un intervalle VBlank sûr. Ces fonctions n’attendent pas, ne mettent rien en file, ne bloquent pas NMI et ne rétablissent pas le défilement. Empêchez une interruption de modifier le verrou ou l’adresse partagés pendant l’appel. Le bit 2 de PPUCTRL choisit un pas de destination de 1 ou 32 par octet. La PPU gère les miroirs et le bouclage des adresses ; la CHR-ROM n’est pas inscriptible. Rétablissez le défilement voulu avant le rendu.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
__nametable_rect_nt(4,18,6,8,4,3); // nt&3=0; red triangle tiles at (144,48).

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__nametable_rect_nt: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:131

Notes de déclaration (texte source)
Fill a rectangle in nametable nt&3 with the same unchecked, direct-write row loop.

kitaqfc/kitaqfc/CodeGenerator.cs:4823

csharp
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

Désactive la génération de NMI par la PPU : efface le bit 7 de la copie de PPUCTRL du compilateur, puis l’écrit dans $2000. Les autres bits sont conservés. Le masque IRQ du processeur et le rendu ne sont pas modifiés.

Syntaxe

c
void __nmi_disable(void);

Valeur renvoyée

Aucune (void).

Conditions et limites

Ces fonctions utilisent la copie de PPUCTRL sans relire le matériel ; écrire directement dans $2000 ne la met pas à jour. Activer la NMI avec l’indicateur VBlank positionné peut la déclencher immédiatement. Désactiver sa génération par la PPU n’annule pas une requête déjà mémorisée par le processeur.

NESdev: NMI

NESdev : interruptions du CPU

Exemple d’utilisation

c
__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.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme distingue le masque IRQ du processeur du contrôle NMI de la PPU. En bleu sur fond noir doivent apparaître IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 et RESTORED I = 000. Chaque valeur représente le bit I extrait, pas l’octet d’état complet. FAILED CHECKS doit valoir 000.

Exécutez au moins 360 images pour terminer la démonstration des 256 attentes. NMI DELTA 3 WAITS doit valoir 003 même avec les IRQ masquées ; NMI DELTA 256 WAITS doit valoir 000, car le compteur sur un octet boucle. NMI OFF DELTA doit aussi valoir 000. Le programme utilise le gestionnaire par défaut sans runtime.c. Vingt et un contrôles d’état distincts couvrent notamment la restauration des indicateurs et des attentes observées sur une durée limitée avec NMI désactivée ou un gestionnaire personnalisé vide. KUROSAKI/NROM est vérifié, pas le matériel réel.

__nmi_disable: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:174

Notes de déclaration (texte source)
Clear bit 7 in the PPUCTRL shadow and write it to hardware; CPU IRQ masking is separate.

kitaqfc/kitaqfc/CodeGenerator.cs:4730

csharp
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

Active la NMI produite par la PPU : positionne le bit 7 de la copie de PPUCTRL conservée par le compilateur, puis écrit cette valeur dans $2000. Les autres bits de cette copie et le masque IRQ du processeur sont conservés. Le rendu du fond et des sprites n’est pas activé.

Syntaxe

c
void __nmi_enable(void);

Valeur renvoyée

Aucune (void).

Conditions et limites

Ces fonctions utilisent la copie de PPUCTRL sans relire le matériel ; écrire directement dans $2000 ne la met pas à jour. Activer la NMI avec l’indicateur VBlank positionné peut la déclencher immédiatement. Désactiver sa génération par la PPU n’annule pas une requête déjà mémorisée par le processeur.

NESdev: NMI

NESdev : interruptions du CPU

Exemple d’utilisation

c
__nmi_enable(); // Preserve the PPUCTRL shadow's other bits; CPU I stays set.
__nmi_wait(); // The default NMI handler still runs with IRQ masked.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme distingue le masque IRQ du processeur du contrôle NMI de la PPU. En bleu sur fond noir doivent apparaître IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 et RESTORED I = 000. Chaque valeur représente le bit I extrait, pas l’octet d’état complet. FAILED CHECKS doit valoir 000.

Exécutez au moins 360 images pour terminer la démonstration des 256 attentes. NMI DELTA 3 WAITS doit valoir 003 même avec les IRQ masquées ; NMI DELTA 256 WAITS doit valoir 000, car le compteur sur un octet boucle. NMI OFF DELTA doit aussi valoir 000. Le programme utilise le gestionnaire par défaut sans runtime.c. Vingt et un contrôles d’état distincts couvrent notamment la restauration des indicateurs et des attentes observées sur une durée limitée avec NMI désactivée ou un gestionnaire personnalisé vide. KUROSAKI/NROM est vérifié, pas le matériel réel.

__nmi_enable: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:172

Notes de déclaration (texte source)
Set bit 7 in the PPUCTRL shadow and write it to hardware, preserving the other shadow bits.

kitaqfc/kitaqfc/CodeGenerator.cs:4727

csharp
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

Lit le compteur NMI courant du compilateur sans attendre ni le remettre à zéro. Malgré le nom ready, le résultat est un compteur sur un octet, pas un indicateur booléen. Comparez-le à une valeur sauvegardée pour détecter une progression du gestionnaire NMI par défaut.

Syntaxe

c
u8 __nmi_ready(void);

Valeur renvoyée

Un octet non signé, de 0 à 255, incrémenté modulo 256 par le gestionnaire NMI par défaut. Zéro est valide. Après 256 incréments, la valeur courante rejoint la valeur sauvegardée ; des lectures espacées ne permettent pas de compter une durée arbitrairement longue.

Conditions et limites

Ce compteur appartient au gestionnaire NMI par défaut du compilateur. Définir __nes_nmi le remplace. runtime.c utilise ses propres nes_nmi_counter et nes_wait_nmi. Un gestionnaire personnalisé vide n’incrémente pas le compteur du compilateur et __nmi_wait ne revient pas. Activez la NMI et attendez hors de son gestionnaire.

NESdev: NMI

NESdev : interruptions du CPU

Exemple d’utilisation

c
before_count=__nmi_ready(); // A wrapping byte, not a Boolean ready flag.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme distingue le masque IRQ du processeur du contrôle NMI de la PPU. En bleu sur fond noir doivent apparaître IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 et RESTORED I = 000. Chaque valeur représente le bit I extrait, pas l’octet d’état complet. FAILED CHECKS doit valoir 000.

Exécutez au moins 360 images pour terminer la démonstration des 256 attentes. NMI DELTA 3 WAITS doit valoir 003 même avec les IRQ masquées ; NMI DELTA 256 WAITS doit valoir 000, car le compteur sur un octet boucle. NMI OFF DELTA doit aussi valoir 000. Le programme utilise le gestionnaire par défaut sans runtime.c. Vingt et un contrôles d’état distincts couvrent notamment la restauration des indicateurs et des attentes observées sur une durée limitée avec NMI désactivée ou un gestionnaire personnalisé vide. KUROSAKI/NROM est vérifié, pas le matériel réel.

__nmi_ready: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:170

Notes de déclaration (texte source)
Return the current wrapping NMI counter byte, not a Boolean ready flag.

kitaqfc/kitaqfc/CodeGenerator.cs:4740

csharp
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

Lit le compteur NMI du compilateur à l’entrée, puis attend dans une boucle qu’il change. Avec le gestionnaire par défaut, attend la fin d’une exécution ultérieure du gestionnaire, traitement de la file VRAM validée compris. N’active pas la NMI, ne valide pas les commandes et ne prévoit aucun délai maximal.

Syntaxe

c
void __nmi_wait(void);

Valeur renvoyée

Aucune (void).

Conditions et limites

Ce compteur appartient au gestionnaire NMI par défaut du compilateur. Définir __nes_nmi le remplace. runtime.c utilise ses propres nes_nmi_counter et nes_wait_nmi. Un gestionnaire personnalisé vide n’incrémente pas le compteur du compilateur et __nmi_wait ne revient pas. Activez la NMI et attendez hors de son gestionnaire.

NESdev: NMI

NESdev : interruptions du CPU

Exemple d’utilisation

c
__nmi_wait();__nmi_wait();__nmi_wait();
delta=(u8)(__nmi_ready()-before_count); // Three completed default NMIs.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme distingue le masque IRQ du processeur du contrôle NMI de la PPU. En bleu sur fond noir doivent apparaître IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 et RESTORED I = 000. Chaque valeur représente le bit I extrait, pas l’octet d’état complet. FAILED CHECKS doit valoir 000.

Exécutez au moins 360 images pour terminer la démonstration des 256 attentes. NMI DELTA 3 WAITS doit valoir 003 même avec les IRQ masquées ; NMI DELTA 256 WAITS doit valoir 000, car le compteur sur un octet boucle. NMI OFF DELTA doit aussi valoir 000. Le programme utilise le gestionnaire par défaut sans runtime.c. Vingt et un contrôles d’état distincts couvrent notamment la restauration des indicateurs et des attentes observées sur une durée limitée avec NMI désactivée ou un gestionnaire personnalisé vide. KUROSAKI/NROM est vérifié, pas le matériel réel.

__nmi_wait: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:168

Notes de déclaration (texte source)
NMI / IRQ
Spin until the runtime NMI counter changes; the configured handler must advance it. No timeout is emitted.

kitaqfc/kitaqfc/CodeGenerator.cs:4738

csharp
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

Masque les 64 sprites du tampon miroir de la page 02 en réglant chaque octet Y sur 0xF0. Les numéros de tuiles, les attributs et les coordonnées X sont conservés.

Syntaxe

c
void __oam_clear(void);

Valeur renvoyée

Aucune (void).

Conditions et limites

Ces opérations modifient la RAM CPU 0x0200–0x02FF, sans mettre immédiatement à jour l’OAM matérielle. Transférez la page complète avec __oam_dma. Elles ne tiennent à jour ni les indicateurs d’allocation, ni les tableaux de coordonnées, ni le compteur de la bibliothèque de sprites. Évitez qu’une interruption modifie ou transfère une page encore en préparation.

Exemple d’utilisation

c
__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.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple réserve une ligne légendée à chaque opération. SET et MOVE montrent des flèches de 8×8 vers la droite en (144,16) et (144,32) ; TILE, un carré vert en (144,48) ; ATTR, la flèche retournée selon les deux axes en (144,64). HIDE en (144,80) et CLEAR en (144,112) doivent rester vides. META montre une paire en (136,96) et (144,96), la seconde flèche étant retournée horizontalement. NEXT 07 indique l’indice suivant après les entrées 5 et 6. Les bords sont rouges, la tige verte et les détails de la pointe bleus. La vérification compare 8 704 pixels par image et les octets OAM transférés ; le matériel physique n’a pas été testé.

TRANSFER montre une flèche en (144,128). Ce programme envoie directement la page 02 avec __oam_dma, y compris tous les champs préparés par les opérations individuelles sur les sprites.

__oam_clear: PAGE02
PAGE02

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:190

Notes de déclaration (texte source)
Hide all 64 page-02 shadow sprites by writing Y=F0; hardware OAM changes only after DMA.
csharp
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

Transfère les 256 octets de la page miroir 0x0200–0x02FF vers l’OAM matérielle, à partir de l’entrée 0. Le CPU est suspendu pendant le transfert et reprend son exécution à la fin.

Syntaxe

c
void __oam_dma(void);

Valeur renvoyée

Aucune (void).

Conditions et limites

Appelez pendant le VBlank ou avec le rendu désactivé ; aucune des deux fonctions DMA n’attend le VBlank. Elles remettent OAMADDR à zéro avant le transfert. Le matériel suspend normalement le CPU pendant 513 ou 514 cycles ; l’activité DMC peut prolonger cette durée. KUROSAKI l’approxime à 513 cycles : la vérification des images ne confirme donc pas la temporisation matérielle exacte. KQ2421 peut apparaître même après une attente explicite du VBlank, car le compilateur classe les appels selon le nom de l’appelant.

Exemple d’utilisation

c
m_wait();__oam_dma(); // Transfer all 256 bytes from page 02 at VBlank.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple réserve une ligne légendée à chaque opération. SET et MOVE montrent des flèches de 8×8 vers la droite en (144,16) et (144,32) ; TILE, un carré vert en (144,48) ; ATTR, la flèche retournée selon les deux axes en (144,64). HIDE en (144,80) et CLEAR en (144,112) doivent rester vides. META montre une paire en (136,96) et (144,96), la seconde flèche étant retournée horizontalement. NEXT 07 indique l’indice suivant après les entrées 5 et 6. Les bords sont rouges, la tige verte et les détails de la pointe bleus. La vérification compare 8 704 pixels par image et les octets OAM transférés ; le matériel physique n’a pas été testé.

TRANSFER montre une flèche en (144,128). Ce programme envoie directement la page 02 avec __oam_dma, y compris tous les champs préparés par les opérations individuelles sur les sprites.

__oam_dma: PAGE02
PAGE02

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:186

Notes de déclaration (texte source)
OAM / input
Set OAMADDR to zero and start DMA from CPU page 02; arrange safe PPU timing in the caller.
csharp
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

Transfère 256 octets de la page mémoire CPU sélectionnée vers l’OAM matérielle, à partir de l’entrée 0. Une page OAM préparée séparément peut ainsi être utilisée sans copie préalable vers la page 02. La source n’est pas modifiée.

Syntaxe

c
void __oam_dma_page(u8 page);

Paramètres

page

Octet de poids fort de l’adresse CPU source : passez 4 pour 0x0400–0x04FF, et non 0x0400. Fournissez une page complète de 256 octets lisibles et gardez le banc requis accessible. Évitez les pages contenant des registres d’E/S, dont la lecture peut avoir des effets de bord. La source n’est pas validée.

Valeur renvoyée

Aucune (void).

Conditions et limites

Appelez pendant le VBlank ou avec le rendu désactivé ; aucune des deux fonctions DMA n’attend le VBlank. Elles remettent OAMADDR à zéro avant le transfert. Le matériel suspend normalement le CPU pendant 513 ou 514 cycles ; l’activité DMC peut prolonger cette durée. KUROSAKI l’approxime à 513 cycles : la vérification des images ne confirme donc pas la temporisation matérielle exacte. KQ2421 peut apparaître même après une attente explicite du VBlank, car le compilateur classe les appels selon le nom de l’appelant.

Exemple d’utilisation

c
m_wait();__oam_dma_page(4); // The argument is a page number, not 0x0400.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple réserve une ligne légendée à chaque opération. SET et MOVE montrent des flèches de 8×8 vers la droite en (144,16) et (144,32) ; TILE, un carré vert en (144,48) ; ATTR, la flèche retournée selon les deux axes en (144,64). HIDE en (144,80) et CLEAR en (144,112) doivent rester vides. META montre une paire en (136,96) et (144,96), la seconde flèche étant retournée horizontalement. NEXT 07 indique l’indice suivant après les entrées 5 et 6. Les bords sont rouges, la tige verte et les détails de la pointe bleus. La vérification compare 8 704 pixels par image et les octets OAM transférés ; le matériel physique n’a pas été testé.

Cette variante copie la page 02 préparée dans un tableau séparé à 0x0400, puis ne change que la tuile de l’entrée 7, qui devient 129. __oam_dma_page(4) doit afficher un carré vert en (144,128), face à TRANSFER, tandis que la page 02 conserve la tuile 128 de la flèche. Les autres lignes correspondent à l’exemple par défaut. Les 256 octets OAM sont comparés à la page 04 et l’intégrité de la page 02 est vérifiée.

__oam_dma_page: PAGE04
PAGE04

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:188

Notes de déclaration (texte source)
Set OAMADDR to zero and DMA 256 bytes from page<<8. The source page must remain readable.
csharp
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

Conserve les bits 0–3 de l’état NES fourni (pad & 0x0F) : A, B, Select et Start. Filtre uniquement une valeur déjà lue, sans accéder au matériel du contrôleur.

Syntaxe

c
u8 __pad_buttons(u8 pad);

Paramètres

pad

Un état NES de 8 bits déjà lu, par exemple le résultat de __pad_read1_safe(). Passez la valeur elle-même, pas un pointeur, un numéro de port ou un masque logique input_*.

Valeur renvoyée

Masque de 0 à 15 dans l’ordre indiqué. Plusieurs bits peuvent être actifs ; testez un bouton avec result & bit plutôt qu’en comparant tout le résultat à 1.

Conditions et limites

Format NES brut : A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit à 1 indique un appui. Utilisez ces valeurs avec nes_pad_* et les résultats bruts de __pad_*, et non les masques communs BTN_* de input.h.

Exemple d’utilisation

c
buttons=__pad_buttons(raw1); // Low nibble of 129: 1, the A button.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Ce programme compare les deux ports, les lectures simples et avec vérification de concordance, les filtres et les macros abrégées. Maintenez A + droite sur la manette 1 et B + gauche sur la manette 2. RAW P1, SAFE P1 et ALIAS P1 valent 129 ; les trois valeurs P2 sont 66. BUTTONS P1 vaut 1 et DIRS P1 vaut 128 : le filtre des directions conserve la position des bits de poids fort. FAILED CHECKS doit valoir 0. Les valeurs restent affichées.

__pad_buttons: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-cache
Implémentation et source

kitaqfc/lib/intrinsics.h:213

Notes de déclaration (texte source)
Return pad & 0F for A/B/Select/Start; this masks a supplied snapshot and performs no polling.
__pad_dirs

Conserve les bits 4–7 de l’état NES (pad & 0xF0) : haut, bas, gauche et droite. Les positions sont conservées ; droite seule renvoie 128, pas 8. Aucune lecture du contrôleur n’est effectuée.

Syntaxe

c
u8 __pad_dirs(u8 pad);

Paramètres

pad

Un état NES de 8 bits déjà lu, par exemple le résultat de __pad_read1_safe(). Passez la valeur elle-même, pas un pointeur, un numéro de port ou un masque logique input_*.

Valeur renvoyée

La valeur fournie ET 0xF0 : 0, 16, 32, …, 240. Les directions restent dans les bits 4–7.

Conditions et limites

Format NES brut : A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit à 1 indique un appui. Utilisez ces valeurs avec nes_pad_* et les résultats bruts de __pad_*, et non les masques communs BTN_* de input.h.

Exemple d’utilisation

c
directions=__pad_dirs(raw1); // High nibble retained in place: 128, not 8.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Ce programme compare les deux ports, les lectures simples et avec vérification de concordance, les filtres et les macros abrégées. Maintenez A + droite sur la manette 1 et B + gauche sur la manette 2. RAW P1, SAFE P1 et ALIAS P1 valent 129 ; les trois valeurs P2 sont 66. BUTTONS P1 vaut 1 et DIRS P1 vaut 128 : le filtre des directions conserve la position des bits de poids fort. FAILED CHECKS doit valoir 0. Les valeurs restent affichées.

__pad_dirs: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-cache
Implémentation et source

kitaqfc/lib/intrinsics.h:215

Notes de déclaration (texte source)
Return pad & F0 in the original bit positions; this does not normalize directions to bits 0..3.
__pad_read1

Verrouille l’état des deux contrôleurs en écrivant 1 puis 0 dans $4016, puis lit les huit bits série D0 du contrôleur 1 à $4016. Renvoie un état de maintien au format NES, sans nouvel essai ni calcul de transition.

Syntaxe

c
u8 __pad_read1(void);

Valeur renvoyée

Masque de maintien de 8 bits dans l’ordre NES. Chaque bit à 1 identifie un bouton appuyé ; 0 signifie aucun. Ce n’est pas un booléen normalisé.

Conditions et limites

Format NES brut : A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit à 1 indique un appui. Utilisez ces valeurs avec nes_pad_* et les résultats bruts de __pad_*, et non les masques communs BTN_* de input.h.

Cet appel lit un nouvel état matériel. Il ne met pas à jour les états mémorisés input_* ou nes_pad1_* et ne produit ni événements d’appui ou de relâchement, ni répétitions. Mémorisez le résultat et comparez-le à votre masque précédent pour détecter les transitions.

Exemple d’utilisation

c
raw1=__pad_read1(); // Controller 1: A (1) | Right (128) = 129.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Ce programme compare les deux ports, les lectures simples et avec vérification de concordance, les filtres et les macros abrégées. Maintenez A + droite sur la manette 1 et B + gauche sur la manette 2. RAW P1, SAFE P1 et ALIAS P1 valent 129 ; les trois valeurs P2 sont 66. BUTTONS P1 vaut 1 et DIRS P1 vaut 128 : le filtre des directions conserve la position des bits de poids fort. FAILED CHECKS doit valoir 0. Les valeurs restent affichées.

__pad_read1: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-cache
Implémentation et source

kitaqfc/lib/intrinsics.h:205

Notes de déclaration (texte source)
Latch and sample eight D0 bits from port 1; bit order is A,B,Select,Start,Up,Down,Left,Right.
__pad_read1_d1

Lit huit bits série sur l’entrée d’extension D1 à $4016. Écrit 1 puis 0 dans $4016 pour verrouiller les états, lit huit fois le bit 1 et place les échantillons successifs dans les bits 0–7 du résultat. Il s’agit d’une lecture du canal D1 du port 1 ; le canal D0 de la manette ordinaire n’est pas lu et aucun nouvel essai ne recherche des valeurs concordantes.

Syntaxe

c
u8 __pad_read1_d1(void);

Valeur renvoyée

Masque de maintien de 8 bits dans l’ordre NES. Chaque bit à 1 identifie un bouton appuyé ; 0 signifie aucun. Ce n’est pas un booléen normalisé.

Conditions et limites

Format NES brut : A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit à 1 indique un appui. Utilisez ces valeurs avec nes_pad_* et les résultats bruts de __pad_*, et non les masques communs BTN_* de input.h.

Utilisez un périphérique fournissant un flux série de manette compatible sur huit bits par la ligne D1 choisie. La fonction n’identifie pas le périphérique et ne prend pas en charge tous les protocoles d’extension. Chaque appel verrouille à nouveau les deux flux de contrôleurs ; terminez une lecture complète avant d’en commencer une autre. Les résultats sont de nouvelles lectures qui ne mettent pas à jour les états input_* ou nes_pad1_*.

Le modèle de contrôleurs du KUROSAKI publié ne renvoie que D0 et le bit 6 fixe. D1 et le bit du microphone valent donc toujours 0 dans cet émulateur, même lorsqu’un bouton ordinaire est maintenu. L’exécution fournie vérifie la compilation, l’entrée normale et cette observation de signal nul. Les signaux périphériques non nuls, les délais électriques et la compatibilité matérielle restent non vérifiés ; un zéro ici ne prouve ni le bon fonctionnement du périphérique ni l’impossibilité de le lire sur une machine réelle.

Exemple d’utilisation

c
d1_first=__pad_read1_d1();

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Ce programme compare l’entrée ordinaire D0, l’extension D1 et le niveau numérique du microphone. Maintenez A sur la manette 1 et B sur la manette 2 jusqu’à la capture. NORMAL P1 et NORMAL P2 affichent 001 et 002. Dans l’exécution KUROSAKI fournie, D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL et MIC DIRECT affichent 000, car ces lignes ne sont pas émulées. Les valeurs restent ensuite fixes. Avec des périphériques adaptés et leurs lignes implémentées, les valeurs dépendent des entrées réelles ; interprétez-les selon les bits décrits ci-dessus au lieu d’attendre ces zéros.

Le modèle de contrôleurs du KUROSAKI publié ne renvoie que D0 et le bit 6 fixe. D1 et le bit du microphone valent donc toujours 0 dans cet émulateur, même lorsqu’un bouton ordinaire est maintenu. L’exécution fournie vérifie la compilation, l’entrée normale et cette observation de signal nul. Les signaux périphériques non nuls, les délais électriques et la compatibilité matérielle restent non vérifiés ; un zéro ici ne prouve ni le bon fonctionnement du périphérique ni l’impossibilité de le lire sur une machine réelle.

__pad_read1_d1: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:223

Notes de déclaration (texte source)
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

Lit le contrôleur 1 par paires d’appels complets à __pad_read1(). Renvoie le masque NES lorsque les deux lectures concordent. Le résultat décrit le maintien, pas les nouveaux appuis.

Syntaxe

c
u8 __pad_read1_safe(void);

Valeur renvoyée

Masque de maintien de 8 bits dans l’ordre NES. Chaque bit à 1 identifie un bouton appuyé ; 0 signifie aucun. Ce n’est pas un booléen normalisé.

Conditions et limites

Format NES brut : A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit à 1 indique un appui. Utilisez ces valeurs avec nes_pad_* et les résultats bruts de __pad_*, et non les masques communs BTN_* de input.h.

Cet appel lit un nouvel état matériel. Il ne met pas à jour les états mémorisés input_* ou nes_pad1_* et ne produit ni événements d’appui ou de relâchement, ni répétitions. Mémorisez le résultat et comparez-le à votre masque précédent pour détecter les transitions.

La lecture sûre répète des paires de lectures complètes jusqu’à obtenir deux masques identiques. Chaque lecture active le strobe de $4016 et redémarre la lecture série des deux contrôleurs. Le nombre d’essais n’est pas limité. Ce contrôle ne prouve pas la compatibilité avec toutes les combinaisons DMC/périphériques.

Exemple d’utilisation

c
safe1=__pad_read1_safe(); // Repeat paired controller-1 reads until they agree.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Ce programme compare les deux ports, les lectures simples et avec vérification de concordance, les filtres et les macros abrégées. Maintenez A + droite sur la manette 1 et B + gauche sur la manette 2. RAW P1, SAFE P1 et ALIAS P1 valent 129 ; les trois valeurs P2 sont 66. BUTTONS P1 vaut 1 et DIRS P1 vaut 128 : le filtre des directions conserve la position des bits de poids fort. FAILED CHECKS doit valoir 0. Les valeurs restent affichées.

__pad_read1_safe: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-cache
Implémentation et source

kitaqfc/lib/intrinsics.h:209

Notes de déclaration (texte source)
Repeat pairs of port-1 polls until both bytes match; the retry loop has no fixed limit.
__pad_read2

Verrouille l’état des deux contrôleurs en écrivant 1 puis 0 dans $4016, puis lit les huit bits série D0 du contrôleur 2 à $4017. Renvoie un état de maintien au format NES, sans nouvel essai ni calcul de transition.

Syntaxe

c
u8 __pad_read2(void);

Valeur renvoyée

Masque de maintien de 8 bits dans l’ordre NES. Chaque bit à 1 identifie un bouton appuyé ; 0 signifie aucun. Ce n’est pas un booléen normalisé.

Conditions et limites

Format NES brut : A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit à 1 indique un appui. Utilisez ces valeurs avec nes_pad_* et les résultats bruts de __pad_*, et non les masques communs BTN_* de input.h.

Cet appel lit un nouvel état matériel. Il ne met pas à jour les états mémorisés input_* ou nes_pad1_* et ne produit ni événements d’appui ou de relâchement, ni répétitions. Mémorisez le résultat et comparez-le à votre masque précédent pour détecter les transitions.

Exemple d’utilisation

c
raw2=__pad_read2(); // Controller 2: B (2) | Left (64) = 66.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Ce programme compare les deux ports, les lectures simples et avec vérification de concordance, les filtres et les macros abrégées. Maintenez A + droite sur la manette 1 et B + gauche sur la manette 2. RAW P1, SAFE P1 et ALIAS P1 valent 129 ; les trois valeurs P2 sont 66. BUTTONS P1 vaut 1 et DIRS P1 vaut 128 : le filtre des directions conserve la position des bits de poids fort. FAILED CHECKS doit valoir 0. Les valeurs restent affichées.

__pad_read2: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-cache
Implémentation et source

kitaqfc/lib/intrinsics.h:207

Notes de déclaration (texte source)
Latch and sample eight D0 bits from port 2 in the same NES button order.
__pad_read2_d1

Lit huit bits série sur l’entrée d’extension D1 à $4017. Écrit 1 puis 0 dans $4016 pour verrouiller les états, lit huit fois le bit 1 et place les échantillons successifs dans les bits 0–7 du résultat. Il s’agit d’une lecture du canal D1 du port 2 ; le canal D0 de la manette ordinaire n’est pas lu et aucun nouvel essai ne recherche des valeurs concordantes.

Syntaxe

c
u8 __pad_read2_d1(void);

Valeur renvoyée

Masque de maintien de 8 bits dans l’ordre NES. Chaque bit à 1 identifie un bouton appuyé ; 0 signifie aucun. Ce n’est pas un booléen normalisé.

Conditions et limites

Format NES brut : A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit à 1 indique un appui. Utilisez ces valeurs avec nes_pad_* et les résultats bruts de __pad_*, et non les masques communs BTN_* de input.h.

Utilisez un périphérique fournissant un flux série de manette compatible sur huit bits par la ligne D1 choisie. La fonction n’identifie pas le périphérique et ne prend pas en charge tous les protocoles d’extension. Chaque appel verrouille à nouveau les deux flux de contrôleurs ; terminez une lecture complète avant d’en commencer une autre. Les résultats sont de nouvelles lectures qui ne mettent pas à jour les états input_* ou nes_pad1_*.

Le modèle de contrôleurs du KUROSAKI publié ne renvoie que D0 et le bit 6 fixe. D1 et le bit du microphone valent donc toujours 0 dans cet émulateur, même lorsqu’un bouton ordinaire est maintenu. L’exécution fournie vérifie la compilation, l’entrée normale et cette observation de signal nul. Les signaux périphériques non nuls, les délais électriques et la compatibilité matérielle restent non vérifiés ; un zéro ici ne prouve ni le bon fonctionnement du périphérique ni l’impossibilité de le lire sur une machine réelle.

Exemple d’utilisation

c
d1_second=__pad_read2_d1();

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Ce programme compare l’entrée ordinaire D0, l’extension D1 et le niveau numérique du microphone. Maintenez A sur la manette 1 et B sur la manette 2 jusqu’à la capture. NORMAL P1 et NORMAL P2 affichent 001 et 002. Dans l’exécution KUROSAKI fournie, D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL et MIC DIRECT affichent 000, car ces lignes ne sont pas émulées. Les valeurs restent ensuite fixes. Avec des périphériques adaptés et leurs lignes implémentées, les valeurs dépendent des entrées réelles ; interprétez-les selon les bits décrits ci-dessus au lieu d’attendre ces zéros.

Le modèle de contrôleurs du KUROSAKI publié ne renvoie que D0 et le bit 6 fixe. D1 et le bit du microphone valent donc toujours 0 dans cet émulateur, même lorsqu’un bouton ordinaire est maintenu. L’exécution fournie vérifie la compilation, l’entrée normale et cette observation de signal nul. Les signaux périphériques non nuls, les délais électriques et la compatibilité matérielle restent non vérifiés ; un zéro ici ne prouve ni le bon fonctionnement du périphérique ni l’impossibilité de le lire sur une machine réelle.

__pad_read2_d1: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:225

Notes de déclaration (texte source)
Latch and read an eight-button stream from port-2 data line D1 rather than D0.
__pad_read2_safe

Lit le contrôleur 2 par paires d’appels complets à __pad_read2(). Renvoie le masque NES lorsque les deux lectures concordent. Le résultat décrit le maintien, pas les nouveaux appuis.

Syntaxe

c
u8 __pad_read2_safe(void);

Valeur renvoyée

Masque de maintien de 8 bits dans l’ordre NES. Chaque bit à 1 identifie un bouton appuyé ; 0 signifie aucun. Ce n’est pas un booléen normalisé.

Conditions et limites

Format NES brut : A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit à 1 indique un appui. Utilisez ces valeurs avec nes_pad_* et les résultats bruts de __pad_*, et non les masques communs BTN_* de input.h.

Cet appel lit un nouvel état matériel. Il ne met pas à jour les états mémorisés input_* ou nes_pad1_* et ne produit ni événements d’appui ou de relâchement, ni répétitions. Mémorisez le résultat et comparez-le à votre masque précédent pour détecter les transitions.

La lecture sûre répète des paires de lectures complètes jusqu’à obtenir deux masques identiques. Chaque lecture active le strobe de $4016 et redémarre la lecture série des deux contrôleurs. Le nombre d’essais n’est pas limité. Ce contrôle ne prouve pas la compatibilité avec toutes les combinaisons DMC/périphériques.

Exemple d’utilisation

c
safe2=__pad_read2_safe(); // The same consistency check on controller 2.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Ce programme compare les deux ports, les lectures simples et avec vérification de concordance, les filtres et les macros abrégées. Maintenez A + droite sur la manette 1 et B + gauche sur la manette 2. RAW P1, SAFE P1 et ALIAS P1 valent 129 ; les trois valeurs P2 sont 66. BUTTONS P1 vaut 1 et DIRS P1 vaut 128 : le filtre des directions conserve la position des bits de poids fort. FAILED CHECKS doit valoir 0. Les valeurs restent affichées.

__pad_read2_safe: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-cache
Implémentation et source

kitaqfc/lib/intrinsics.h:211

Notes de déclaration (texte source)
Repeat pairs of port-2 polls until both bytes match; this does not populate pad.c globals.
__palette_bg_load

Réinitialise le verrou PPU, définit 0x3F00 et écrit exactement 16 octets de la source via PPUDATA pour les quatre palettes de fond. Le transfert est immédiat ; il ne change pas les sélections de palette des attributs et ne charge pas de graphismes de tuiles.

Syntaxe

c
void __palette_bg_load(const u8* pal16);

Paramètres

pal16

Pointeur CPU vers 16 octets lisibles de codes de palette NES, pas de triplets RGB. Maintenez la banque source accessible jusqu’au retour synchrone ; aucun changement de banque ni contrôle de longueur n’est effectué. Utilisez l’incrément PPU 1 pour atteindre les 16 adresses prévues.

Valeur renvoyée

Aucune (void).

Conditions et limites

Les adresses PPU 0x3F10/14/18/1C sont des alias de 0x3F00/04/08/0C. Charger la palette de sprites écrit donc aussi ces entrées du fond. Le compilateur transmet les octets tels quels, sans valider les couleurs, fusionner les palettes ni préserver les entrées partagées.

L’accès direct à la PPU exige de désactiver le rendu ou de terminer toute l’opération dans un intervalle VBlank sûr. Ces fonctions n’attendent pas, ne mettent rien en file, ne bloquent pas NMI et ne rétablissent pas le défilement. Empêchez une interruption de modifier le verrou ou l’adresse partagés pendant l’appel. Le bit 2 de PPUCTRL choisit un pas de destination de 1 ou 32 par octet. La PPU gère les miroirs et le bouclage des adresses ; la CHR-ROM n’est pas inscriptible. Rétablissez le défilement voulu avant le rendu.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
__palette_bg_load(scene_palette); // Four background palettes, 16 bytes total.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__palette_bg_load: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:138

Notes de déclaration (texte source)
Stream exactly 16 source bytes to 3F00. Arrange PPU access timing and increment one.

kitaqfc/kitaqfc/CodeGenerator.cs:4827

csharp
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

Réinitialise le verrou PPU, définit 0x3F10 et écrit exactement 16 octets de la source via PPUDATA pour les quatre palettes de sprites. Les bits d’attribut du sprite choisissent la palette affichée. Ne modifie pas OAM et ne transfère pas les graphismes de sprites.

Syntaxe

c
void __palette_sp_load(const u8* pal16);

Paramètres

pal16

Pointeur CPU vers 16 octets lisibles de codes de palette NES, pas de triplets RGB. Maintenez la banque source accessible jusqu’au retour synchrone ; aucun changement de banque ni contrôle de longueur n’est effectué. Utilisez l’incrément PPU 1 pour atteindre les 16 adresses prévues.

Valeur renvoyée

Aucune (void).

Conditions et limites

Les adresses PPU 0x3F10/14/18/1C sont des alias de 0x3F00/04/08/0C. Charger la palette de sprites écrit donc aussi ces entrées du fond. Le compilateur transmet les octets tels quels, sans valider les couleurs, fusionner les palettes ni préserver les entrées partagées.

L’accès direct à la PPU exige de désactiver le rendu ou de terminer toute l’opération dans un intervalle VBlank sûr. Ces fonctions n’attendent pas, ne mettent rien en file, ne bloquent pas NMI et ne rétablissent pas le défilement. Empêchez une interruption de modifier le verrou ou l’adresse partagés pendant l’appel. Le bit 2 de PPUCTRL choisit un pas de destination de 1 ou 32 par octet. La PPU gère les miroirs et le bouclage des adresses ; la CHR-ROM n’est pas inscriptible. Rétablissez le défilement voulu avant le rendu.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
__palette_sp_load(scene_palette); // Four sprite palettes, 16 bytes total.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__palette_sp_load: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:140

Notes de déclaration (texte source)
Stream exactly 16 source bytes to 3F10; hardware palette aliases also affect background entries.

kitaqfc/kitaqfc/CodeGenerator.cs:4828

csharp
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

Évalue ppu_addr une fois, lit PPUSTATUS pour réinitialiser le verrou partagé, puis écrit les octets de poids fort et faible dans PPUADDR. Définit la destination des prochains accès à PPUDATA sans écrire de données. La PPU applique ses masques et miroirs d’adresse.

Syntaxe

c
void __ppu_addr(u16 ppu_addr);

Paramètres

ppu_addr

Adresse PPU en octets (0x0000–0x3FFF), et non pointeur CPU. La destination doit être inscriptible : modifier les motifs exige de la CHR RAM, pas de la CHR ROM. Le mappage et le mirroring PPU s’appliquent. Pour les copies et remplissages contigus, choisissez l’incrément 1 dans PPUCTRL. Exécutez à un instant sûr, puis rétablissez le défilement voulu ; l’exécuteur écrit PPUADDR et PPUDATA sans restaurer le défilement.

Valeur renvoyée

Aucune (void).

Conditions et limites

L’accès direct à la PPU exige de désactiver le rendu ou de terminer toute l’opération dans un intervalle VBlank sûr. Ces fonctions n’attendent pas, ne mettent rien en file, ne bloquent pas NMI et ne rétablissent pas le défilement. Empêchez une interruption de modifier le verrou ou l’adresse partagés pendant l’appel. Le bit 2 de PPUCTRL choisit un pas de destination de 1 ou 32 par octet. La PPU gère les miroirs et le bouclage des adresses ; la CHR-ROM n’est pas inscriptible. Rétablissez le défilement voulu avant le rendu.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
address_port=0x21;
__ppu_addr(0x2192); // Reset latch and set the cell at tile (18,12).

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__ppu_addr: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:106

Notes de déclaration (texte source)
Set the PPU address through the compiler helper; address writes affect the shared scroll/address latch.

kitaqfc/kitaqfc/CodeGenerator.cs:4698

csharp
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

Renvoie la copie logicielle de PPUCTRL. Utilisez-la pour sauvegarder l’activation NMI, le choix de la table de motifs et l’incrément d’adresse avant une modification temporaire.

Syntaxe

c
u8 __ppu_ctrl_get(void);

Valeur renvoyée

Renvoie en u8 la dernière valeur de contrôle conservée par la bibliothèque ou les fonctions intrinsèques de réglage. Ne lit pas PPUSTATUS.

Conditions et limites

PPUCTRL et PPUMASK sont accessibles en écriture seule. Cette fonction ne lit pas le matériel. Les écritures directes dans $2000/$2001 ou le code assembleur personnalisé contournent les copies logicielles. Utilisez systématiquement les opérations __ppu_*_set correspondantes. Lire la copie ne change ni le verrou ni l’état des interruptions.

Exemple d’utilisation

c
__ppu_ctrl_set(4);
    if(__ppu_ctrl_get()!=4)failures=1; // Inspect the software shadow, not PPUSTATUS.
    __ppu_ctrl_set(0);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

La solution prépare l’écran sans rendu, installe les palettes, efface tuiles et attributs, puis dessine ce test. Un motif bleu de tuiles pleines, demi-barres gauches et triangles commence en (0,16) ; un bloc vert plein commence en (0,96). Chaque région mesure 256×64 pixels, sauf sa dernière case de 8×8 en (248,72) ou (248,152), qui reste noire car le transfert contient 255 octets de tuiles. À y=208, les formes rouges sont un carré de 8×8 en x=16, une barre gauche de 4×8 en x=224 et un triangle de huit lignes en x=240. FAILED CHECKS indique 0. Des tests d’état distincts confirment les deux registres de contrôle, le transfert des 32 octets de palette et l’effacement des 1 024 octets de la table de noms. Les quatre appels déclarés doivent échouer à l’édition de liens.

__ppu_ctrl_get: Autres usages
Autres usages

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:103

Notes de déclaration (texte source)
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

csharp
case "__ppu_ctrl_get":
                    if (!Need(0)) return true;
                    EmitAsm("LDA", Mem(_runtimePpuCtrlShadowAddress)); return true;
__ppu_ctrl_set

Remplace PPUCTRL et sa copie logicielle par l’octet fourni. Contrôle NMI, l’incrément d’adresse PPU, les tables de motifs, la taille des sprites et la table de noms de base. Ne modifie pas PPUMASK et ne conserve pas les bits non spécifiés.

Syntaxe

c
void __ppu_ctrl_set(u8 value);

Paramètres

value

Octet non signé de 0..255, écrit tel quel. C’est un octet complet de registre ou de données, pas un booléen ni un numéro de bit.

Valeur renvoyée

Aucune (void).

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
__ppu_ctrl_set(0x80); // NMI enabled, increment 1, nametable/pattern tables 0.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__ppu_ctrl_set: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:100

Notes de déclaration (texte source)
Replace PPUCTRL, including NMI enable, pattern-table choices and address increment mode.

kitaqfc/kitaqfc/CodeGenerator.cs:4687

csharp
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

Écrit value une fois dans PPUDATA à l’adresse PPU courante. Celle-ci avance ensuite de 1 ou 32 selon PPUCTRL. Cet appel ne définit pas l’adresse, ne réinitialise pas le verrou et ne met pas le transfert en file.

Syntaxe

c
void __ppu_data(u8 value);

Paramètres

value

Octet non signé de 0..255, écrit tel quel. C’est un octet complet de registre ou de données, pas un booléen ni un numéro de bit.

Valeur renvoyée

Aucune (void).

Conditions et limites

L’accès direct à la PPU exige de désactiver le rendu ou de terminer toute l’opération dans un intervalle VBlank sûr. Ces fonctions n’attendent pas, ne mettent rien en file, ne bloquent pas NMI et ne rétablissent pas le défilement. Empêchez une interruption de modifier le verrou ou l’adresse partagés pendant l’appel. Le bit 2 de PPUCTRL choisit un pas de destination de 1 ou 32 par octet. La PPU gère les miroirs et le bouclage des adresses ; la CHR-ROM n’est pas inscriptible. Rétablissez le défilement voulu avant le rendu.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
__ppu_data(3); // Green triangle at (144,96); destination advances by one.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__ppu_data: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:108

Notes de déclaration (texte source)
Write one PPUDATA byte using the current PPU address and increment mode.

kitaqfc/kitaqfc/CodeGenerator.cs:4701

csharp
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

Renvoie la copie logicielle de PPUMASK. Utilisez-la pour conserver les réglages de rendu, de visibilité du bord gauche, de niveaux de gris et d’accentuation des couleurs.

Syntaxe

c
u8 __ppu_mask_get(void);

Valeur renvoyée

Renvoie en u8 la dernière valeur de contrôle conservée par la bibliothèque ou les fonctions intrinsèques de réglage. Ne lit pas PPUSTATUS.

Conditions et limites

PPUCTRL et PPUMASK sont accessibles en écriture seule. Cette fonction ne lit pas le matériel. Les écritures directes dans $2000/$2001 ou le code assembleur personnalisé contournent les copies logicielles. Utilisez systématiquement les opérations __ppu_*_set correspondantes. Lire la copie ne change ni le verrou ni l’état des interruptions.

Exemple d’utilisation

c
__ppu_mask_set(0xE7);
    if(__ppu_mask_get()!=0xE7)failures=1;
    __ppu_mask_set(0);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

La solution prépare l’écran sans rendu, installe les palettes, efface tuiles et attributs, puis dessine ce test. Un motif bleu de tuiles pleines, demi-barres gauches et triangles commence en (0,16) ; un bloc vert plein commence en (0,96). Chaque région mesure 256×64 pixels, sauf sa dernière case de 8×8 en (248,72) ou (248,152), qui reste noire car le transfert contient 255 octets de tuiles. À y=208, les formes rouges sont un carré de 8×8 en x=16, une barre gauche de 4×8 en x=224 et un triangle de huit lignes en x=240. FAILED CHECKS indique 0. Des tests d’état distincts confirment les deux registres de contrôle, le transfert des 32 octets de palette et l’effacement des 1 024 octets de la table de noms. Les quatre appels déclarés doivent échouer à l’édition de liens.

__ppu_mask_get: Autres usages
Autres usages

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:104

kitaqfc/kitaqfc/CodeGenerator.cs:4694

csharp
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

Remplace tout l’octet PPUMASK et sa copie logicielle par value. Choisissez explicitement rendu, bord gauche, niveaux de gris et accentuation. Ne fusionne pas les bits avec le masque précédent et ne modifie pas PPUCTRL.

Syntaxe

c
void __ppu_mask_set(u8 value);

Paramètres

value

Octet non signé de 0..255, écrit tel quel. C’est un octet complet de registre ou de données, pas un booléen ni un numéro de bit.

Valeur renvoyée

Aucune (void).

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
__ppu_mask_set(0x0A); // Background enabled, including its leftmost eight pixels.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__ppu_mask_set: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:98

Notes de déclaration (texte source)
Replace PPUMASK with the supplied byte; the caller chooses rendering, clipping and emphasis bits.

kitaqfc/kitaqfc/CodeGenerator.cs:4683

csharp
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

Écrit zéro dans PPUMASK et sa copie logicielle, désactivant le fond et les sprites. Efface tous les bits, y compris les niveaux de gris et l’accentuation. PPUCTRL et l’activation de NMI restent inchangés ; contrôlez NMI séparément pour préparer des transferts partagés.

Syntaxe

c
void __ppu_off(void);

Valeur renvoyée

Aucune (void).

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
__ppu_off(); // PPUMASK and its software shadow become zero; NMI is unchanged.
__ppu_ctrl_set(0); // Disable NMI separately while preparing the whole scene.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__ppu_off: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:96

Notes de déclaration (texte source)
Invoke the compiler rendering-disable helper before bulk direct PPU updates.

kitaqfc/kitaqfc/CodeGenerator.cs:4679

csharp
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

Remplace PPUMASK et sa copie logicielle par 0x1E, activant le fond et les sprites, y compris les huit pixels du bord gauche. Écrase les choix de niveaux de gris et d’accentuation des couleurs ; ne modifie pas PPUCTRL et n’active pas NMI.

Syntaxe

c
void __ppu_on(void);

Valeur renvoyée

Aucune (void).

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
__ppu_on(); // Replace mask with 0x1E: enable sprites and background at both edges.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__ppu_on: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:94

Notes de déclaration (texte source)
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

csharp
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

Lit PPUSTATUS une fois et renvoie l’octet observé avant que la lecture efface le drapeau VBlank. Réinitialise aussi le verrou partagé de défilement et d’adresse. Il s’agit d’une lecture matérielle avec effets de bord, pas d’un compteur de trames mémorisé ni d’un test de fin de NMI.

Syntaxe

c
u8 __ppu_read_status(void);

Valeur renvoyée

Octet d’état brut. Le bit 7 indique VBlank, le 6 la rencontre avec le sprite zéro et le 5 le débordement de sprites. Testez le drapeau voulu avec un masque ; ne traitez pas l’octet entier comme un booléen ou un numéro de trame. Les bits faibles ne constituent pas un état applicatif portable.

Conditions et limites

Une lecture de PPUSTATUS peut affecter NMI et les prochaines écritures dans PPUSCROLL et PPUADDR. Ne l’insérez pas entre les deux octets d’une autre routine. Ces appels n’attendent pas VBlank et ne terminent pas les files de transfert en attente.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
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).

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__ppu_read_status: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:110

Notes de déclaration (texte source)
Read PPUSTATUS; the hardware clears VBlank status and resets the shared write latch.

kitaqfc/kitaqfc/CodeGenerator.cs:4705

csharp
case "__ppu_read_status":
                    if (!Need(0)) return true;
                    EmitAsm("LDA", Mem(0x2002)); return true;
__prg_bank_set

Effectue la même sélection de banque PRG adaptée au mapper que __bankswitch. Applique les mêmes vérifications et ne rétablit pas automatiquement la banque précédente.

Syntaxe

c
void __prg_bank_set(u8 bank);

Paramètres

bank

Numéro de banque ROM non signé sur 8 bits. Pour un appel, il doit correspondre au placement de la fonction ; pour une lecture, il sélectionne la source. Respectez la plage du type de cartouche.

Valeur renvoyée

Aucune (void).

Conditions et limites

Un changement persistant de banque ne doit pas rendre inaccessibles le code exécuté ou son adresse de retour. Placez cet appel, bank_switch elle-même si elle est utilisée, et le code qui suit dans la banque commune ou fixe 0, comme dans l’exemple.

L’état de la bibliothèque commence à zéro et ne détecte pas le mappage initial de la cartouche. Initialisez-le avec bank_switch. Les intrinsèques et écritures directes dans le mapper n’actualisent pas cet état distinct ; bank_get_current ne vérifie donc pas le mappage matériel réel.

Les banques FC sont des numéros logiques du compilateur. Zéro désigne la ROM commune, pas une cible de commutation ; la routine le ramène à la banque 1. SUROM512 accepte 1–30 : une constante hors plage transmise directement à l’intrinsèque de commutation provoque une erreur de compilation ; une valeur d’exécution invalide sélectionne 1 et active l’état de défaut du mapper. Lisez directement la ROM commune, sans demander la banque zéro.

Exemple d’utilisation

c
__prg_bank_set(1);bank_expect(home1[0]==0xA1,21);
__prg_bank_set(2); // Same mapper helper as __bankswitch.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

bank_switch(2) enregistre et sélectionne d’abord la banque 2. Une commutation directe sélectionne ensuite la banque 1 et lit A1 dans home1, tandis que bank_get_current indique toujours 2 ; après restauration, home2 donne à nouveau B2.

Le programme complet montre comment identifier des données par une banque et une adresse proche, appeler des rappels entre banques et distinguer l’état de la bibliothèque du mappage réel. Tous les nombres sont hexadécimaux. BANK IN et BANK OUT doivent valoir 02 ; BYTE HEX, D3 ; WORD HEX, 5C7A ; FAILED CHECKS et FIRST FAILURE, 00. Le texte est bleu sur CGB et FC, noir sur DMG.

CALL COUNT vaut 02 : la macro far_call et __farcall invoquent chacun le rappel une fois.

__prg_bank_set: MMC3
MMC3

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:81

Notes de déclaration (texte source)
Use the same mapper PRG-switch helper as __bankswitch; retain executable code in a valid mapping.

kitaqfc/kitaqfc/CodeGenerator.cs:4746

csharp
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

Fait avancer une fois le LFSR de Galois FC partagé sur 16 bits et renvoie le XOR des octets bas et haut mis à jour. Un LFSR est un registre à décalage dont la rétroaction est calculée par XOR ; une graine fixe produit donc une suite d’octets reproductible.

Syntaxe

c
u8 __rng8(void);

Valeur renvoyée

Un résultat u8 (0–255), après un avancement du générateur partagé. Cela décrit le type et les bornes, pas une équiprobabilité de chaque octet.

Conditions et limites

L’étape FC décale l’état de 16 bits d’un bit vers la droite. Si l’ancien bit de poids faible valait 1, elle applique XOR avec 0xB400 à l’état décalé. L’octet renvoyé est low_byte XOR high_byte, pas l’octet bas brut.

Si les deux octets d’état sont nuls, le tirage suivant remplace l’état par 0xA55A avant le décalage. Ainsi, __rng_seed(0) suivi de __rng8() renvoie 255. L’appel de réglage stocke bien zéro ; le remplacement a lieu lors du tirage.

Ces appels partagent un état persistant. La reproduction exige la même cible, la même graine et le même ordre d’appels, y compris ceux des autres fonctions auxiliaires. Gardez réglage de graine et tirages dans un même contexte d’exécution ; ces vérifications ne prouvent pas la sûreté d’un accès concurrent depuis une interruption.

GB et FC utilisent des algorithmes différents et ne garantissent pas la même suite avec la même graine. Ce sont des générateurs déterministes pour le jeu, pas des sources d’aléa cryptographique. Les tests vérifient le comportement de l’implémentation, pas sa qualité statistique ni sa période.

Exemple d’utilisation

c
__rng_seed(0x1234);
first=__rng8();second=__rng8();third=__rng8(); // FC sequence: 19, 137, 240.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Cet exemple lit trois octets FC consécutifs. La graine 0x1234 donne FIRST 00019, SECOND 00137 et THIRD 00240. Ces valeurs montrent l’ordre des appels, pas une fréquence ni une note de qualité aléatoire.

Le programme NROM complet affiche les résultats décimaux en bleu sur noir. FAILED doit indiquer 00000. Le texte est transféré avec le rendu désactivé avant l’activation de l’écran de résultats.

La validation a comparé, pour chaque cas, 32 sorties et huit tirages d’octet suivants à un modèle hôte indépendant : 93 cas GB et 18 cas FC, avec graines constantes ou calculées à l’exécution et limites GB de plage, chance et poids. Les écrans pédagogiques ont été contrôlés après 240 images émulées. Le matériel réel et la qualité statistique n’ont pas été testés.

__rng8: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:39

Notes de déclaration (texte source)
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

csharp
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

Remplace les deux octets d’état du générateur FC. Réutiliser une graine permet à un test ou une relecture de repartir au même point de la suite pseudo-aléatoire ; cet appel ne fait pas avancer le générateur.

Syntaxe

c
void __rng_seed(u16 seed);

Paramètres

seed

Graine non signée de 16 bits, de 0 à 65535. Les deux octets sont stockés tels quels. Choisissez et notez la graine avant la suite de tirages de jeu à reproduire.

Valeur renvoyée

Aucune (void).

Conditions et limites

Si les deux octets d’état sont nuls, le tirage suivant remplace l’état par 0xA55A avant le décalage. Ainsi, __rng_seed(0) suivi de __rng8() renvoie 255. L’appel de réglage stocke bien zéro ; le remplacement a lieu lors du tirage.

Ces appels partagent un état persistant. La reproduction exige la même cible, la même graine et le même ordre d’appels, y compris ceux des autres fonctions auxiliaires. Gardez réglage de graine et tirages dans un même contexte d’exécution ; ces vérifications ne prouvent pas la sûreté d’un accès concurrent depuis une interruption.

GB et FC utilisent des algorithmes différents et ne garantissent pas la même suite avec la même graine. Ce sont des générateurs déterministes pour le jeu, pas des sources d’aléa cryptographique. Les tests vérifient le comportement de l’implémentation, pas sa qualité statistique ni sa période.

Exemple d’utilisation

c
__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.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Cet exemple montre un démarrage FC reproductible et la règle de graine nulle. FIRST et RESEEDED affichent tous deux 00019 pour la graine 0x1234. ZERO SEED affiche 00255 après une graine nulle.

Le programme NROM complet affiche les résultats décimaux en bleu sur noir. FAILED doit indiquer 00000. Le texte est transféré avec le rendu désactivé avant l’activation de l’écran de résultats.

La validation a comparé, pour chaque cas, 32 sorties et huit tirages d’octet suivants à un modèle hôte indépendant : 93 cas GB et 18 cas FC, avec graines constantes ou calculées à l’exécution et limites GB de plage, chance et poids. Les écrans pédagogiques ont été contrôlés après 240 images émulées. Le matériel réel et la qualité statistique n’ont pas été testés.

__rng_seed: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:37

Notes de déclaration (texte source)
Replace both persistent state bytes. Reusing a seed reproduces the sequence; this is not cryptographic randomness.

kitaqfc/kitaqfc/CodeGenerator.cs:4661

csharp
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

Choisit le masque de rendu allumé ou éteint et attend un changement du compteur NMI. Le masque choisi reste actif après le retour.

Syntaxe

c
void __rob_flash(u8 bright);

Paramètres

bright

Zéro choisit l’arrêt ; tout octet non nul, y compris 255, choisit l’activation. Ce n’est pas un niveau de luminosité.

Valeur renvoyée

Aucune valeur de retour. Rend la main à l’appelant après les attentes demandées.

Conditions et limites

Incluez intrinsics.h. Le gestionnaire NMI par défaut doit fonctionner et faire avancer son compteur. Si NMI est désactivé ou si un gestionnaire personnalisé ne maintient pas ce compteur, l’attente reste bloquée. Il n’y a aucun délai d’expiration.

La phase allumée met PPUMASK et sa copie logicielle à 0x1E ; la phase éteinte les met à zéro. L’ancien masque n’est pas restauré. Cela modifie ensemble le rendu du fond, des sprites et du bord gauche, ainsi que les réglages de niveaux de gris et d’accentuation des couleurs.

Activer le rendu ne crée pas à lui seul un écran blanc. Fournissez des tuiles et des couleurs claires, avec une couleur de fond sombre pour la phase éteinte. L’exemple utilise des tuiles blanches pleines et un fond noir pour faire clignoter tout l’écran pendant l’opération.

Les attentes comptent les changements du compteur NMI. Le délai jusqu’au premier NMI peut être inférieur à une image. Appelez depuis le code principal, pas depuis une interruption. Une autre routine modifiant le masque de rendu peut perturber le motif.

Ce sont des primitives de masque d’affichage et d’attente. Elles ne génèrent ni préambule de commande robot, ni séquence propre à un appareil, ni accusé de réception. La validation couvre les écritures PPUMASK, les comptes NMI et les pixels dans KUROSAKI ; la réception par un robot physique n’a pas été testée.

Exemple d’utilisation

c
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.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Après exécution, l’écran affiche FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 et A5 BYTE WAITS 48. Le schéma représente 0xA5 : 1,0,1,0 en haut, puis 0,1,0,1 en bas. Chaque case vaut une attente ; blanc signifie rendu activé, noir désactivé. Un bit 1 comporte quatre cases blanches puis deux noires ; un bit 0, deux blanches puis quatre noires. FINAL MASK 0 et TIMING CHECKS OK indiquent la fin.

__rob_flash: NTSC — Après exécution, l’écran affiche FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 et A5 BYTE WAITS 48. Le schéma représente 0xA5 : 1,0,1,0 en haut, puis 0,1,0,1 en bas. Chaque case vaut une attente ; blanc signifie rendu activé, noir désactivé. Un bit 1 comporte quatre cases blanches puis deux noires ; un bit 0, deux blanches puis quatre noires. FINAL MASK 0 et TIMING CHECKS OK indiquent la fin.
NTSC — Après exécution, l’écran affiche FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 et A5 BYTE WAITS 48. Le schéma représente 0xA5 : 1,0,1,0 en haut, puis 0,1,0,1 en bas. Chaque case vaut une attente ; blanc signifie rendu activé, noir désactivé. Un bit 1 comporte quatre cases blanches puis deux noires ; un bit 0, deux blanches puis quatre noires. FINAL MASK 0 et TIMING CHECKS OK indiquent la fin.

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:272

Notes de déclaration (texte source)
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

csharp
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

Effectue les attentes allumées demandées, puis les attentes éteintes, pour produire une impulsion.

Syntaxe

c
void __rob_pulse(u8 on_frames, u8 off_frames);

Paramètres

on_frames

Nombre d’attentes de la phase allumée, de 0 à 255. Zéro saute cette phase.

off_frames

Nombre d’attentes de la phase éteinte, de 0 à 255. Zéro saute cette phase. Le total peut atteindre 510 attentes.

Valeur renvoyée

Aucune valeur de retour. Rend la main à l’appelant après les attentes demandées.

Conditions et limites

Si les deux comptes sont nuls, ni le masque ni le minutage ne changent. Toute attente éteinte laisse un masque nul ; une impulsion uniquement allumée laisse 0x1E.

Incluez intrinsics.h. Le gestionnaire NMI par défaut doit fonctionner et faire avancer son compteur. Si NMI est désactivé ou si un gestionnaire personnalisé ne maintient pas ce compteur, l’attente reste bloquée. Il n’y a aucun délai d’expiration.

La phase allumée met PPUMASK et sa copie logicielle à 0x1E ; la phase éteinte les met à zéro. L’ancien masque n’est pas restauré. Cela modifie ensemble le rendu du fond, des sprites et du bord gauche, ainsi que les réglages de niveaux de gris et d’accentuation des couleurs.

Activer le rendu ne crée pas à lui seul un écran blanc. Fournissez des tuiles et des couleurs claires, avec une couleur de fond sombre pour la phase éteinte. L’exemple utilise des tuiles blanches pleines et un fond noir pour faire clignoter tout l’écran pendant l’opération.

Les attentes comptent les changements du compteur NMI. Le délai jusqu’au premier NMI peut être inférieur à une image. Appelez depuis le code principal, pas depuis une interruption. Une autre routine modifiant le masque de rendu peut perturber le motif.

Ce sont des primitives de masque d’affichage et d’attente. Elles ne génèrent ni préambule de commande robot, ni séquence propre à un appareil, ni accusé de réception. La validation couvre les écritures PPUMASK, les comptes NMI et les pixels dans KUROSAKI ; la réception par un robot physique n’a pas été testée.

Exemple d’utilisation

c
start=__nmi_ready();
    __rob_pulse(3,2); // Three enabled waits, then two disabled waits.
    rob_result[1]=(u8)(__nmi_ready()-start);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Après exécution, l’écran affiche FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 et A5 BYTE WAITS 48. Le schéma représente 0xA5 : 1,0,1,0 en haut, puis 0,1,0,1 en bas. Chaque case vaut une attente ; blanc signifie rendu activé, noir désactivé. Un bit 1 comporte quatre cases blanches puis deux noires ; un bit 0, deux blanches puis quatre noires. FINAL MASK 0 et TIMING CHECKS OK indiquent la fin.

__rob_pulse: NTSC — Après exécution, l’écran affiche FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 et A5 BYTE WAITS 48. Le schéma représente 0xA5 : 1,0,1,0 en haut, puis 0,1,0,1 en bas. Chaque case vaut une attente ; blanc signifie rendu activé, noir désactivé. Un bit 1 comporte quatre cases blanches puis deux noires ; un bit 0, deux blanches puis quatre noires. FINAL MASK 0 et TIMING CHECKS OK indiquent la fin.
NTSC — Après exécution, l’écran affiche FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 et A5 BYTE WAITS 48. Le schéma représente 0xA5 : 1,0,1,0 en haut, puis 0,1,0,1 en bas. Chaque case vaut une attente ; blanc signifie rendu activé, noir désactivé. Un bit 1 comporte quatre cases blanches puis deux noires ; un bit 0, deux blanches puis quatre noires. FINAL MASK 0 et TIMING CHECKS OK indiquent la fin.

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:274

Notes de déclaration (texte source)
Run on_frames enabled-mask waits followed by off_frames disabled-mask waits; NMI must advance.

kitaqfc/kitaqfc/CodeGenerator.cs:7884

csharp
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

Encode un octet en huit motifs de clignotement, bit de poids fort en premier. Un 1 utilise quatre attentes allumées et deux éteintes ; un 0, deux allumées et quatre éteintes. Le total est de 48 attentes NMI.

Syntaxe

c
void __rob_send_byte(u8 pattern);

Paramètres

pattern

Motif de huit bits, de 0 à 255. Pour 0xA5, l’ordre est 1,0,1,0,0,1,0,1. Ce n’est ni un identifiant de canal ni un numéro d’action robot.

Valeur renvoyée

Aucune valeur de retour. Rend la main à l’appelant après les attentes demandées.

Conditions et limites

Le dernier bit se termine lui aussi par des attentes éteintes, laissant PPUMASK et sa copie à zéro. Envoie exactement huit bits, sans marqueur de début ou de fin.

Incluez intrinsics.h. Le gestionnaire NMI par défaut doit fonctionner et faire avancer son compteur. Si NMI est désactivé ou si un gestionnaire personnalisé ne maintient pas ce compteur, l’attente reste bloquée. Il n’y a aucun délai d’expiration.

La phase allumée met PPUMASK et sa copie logicielle à 0x1E ; la phase éteinte les met à zéro. L’ancien masque n’est pas restauré. Cela modifie ensemble le rendu du fond, des sprites et du bord gauche, ainsi que les réglages de niveaux de gris et d’accentuation des couleurs.

Activer le rendu ne crée pas à lui seul un écran blanc. Fournissez des tuiles et des couleurs claires, avec une couleur de fond sombre pour la phase éteinte. L’exemple utilise des tuiles blanches pleines et un fond noir pour faire clignoter tout l’écran pendant l’opération.

Les attentes comptent les changements du compteur NMI. Le délai jusqu’au premier NMI peut être inférieur à une image. Appelez depuis le code principal, pas depuis une interruption. Une autre routine modifiant le masque de rendu peut perturber le motif.

Ce sont des primitives de masque d’affichage et d’attente. Elles ne génèrent ni préambule de commande robot, ni séquence propre à un appareil, ni accusé de réception. La validation couvre les écritures PPUMASK, les comptes NMI et les pixels dans KUROSAKI ; la réception par un robot physique n’a pas été testée.

Exemple d’utilisation

c
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.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Après exécution, l’écran affiche FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 et A5 BYTE WAITS 48. Le schéma représente 0xA5 : 1,0,1,0 en haut, puis 0,1,0,1 en bas. Chaque case vaut une attente ; blanc signifie rendu activé, noir désactivé. Un bit 1 comporte quatre cases blanches puis deux noires ; un bit 0, deux blanches puis quatre noires. FINAL MASK 0 et TIMING CHECKS OK indiquent la fin.

__rob_send_byte: NTSC — Après exécution, l’écran affiche FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 et A5 BYTE WAITS 48. Le schéma représente 0xA5 : 1,0,1,0 en haut, puis 0,1,0,1 en bas. Chaque case vaut une attente ; blanc signifie rendu activé, noir désactivé. Un bit 1 comporte quatre cases blanches puis deux noires ; un bit 0, deux blanches puis quatre noires. FINAL MASK 0 et TIMING CHECKS OK indiquent la fin.
NTSC — Après exécution, l’écran affiche FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 et A5 BYTE WAITS 48. Le schéma représente 0xA5 : 1,0,1,0 en haut, puis 0,1,0,1 en bas. Chaque case vaut une attente ; blanc signifie rendu activé, noir désactivé. Un bit 1 comporte quatre cases blanches puis deux noires ; un bit 0, deux blanches puis quatre noires. FINAL MASK 0 et TIMING CHECKS OK indiquent la fin.

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:278

Notes de déclaration (texte source)
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

csharp
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

Lit PPUSTATUS et ignore le résultat pour réinitialiser le verrou partagé de défilement et d’adresse. Efface aussi le drapeau VBlank. N’écrit pas de coordonnées de défilement nulles et ne change pas les valeurs X/Y mémorisées.

Syntaxe

c
void __scroll_latch_reset(void);

Valeur renvoyée

Aucune (void).

Conditions et limites

Une lecture de PPUSTATUS peut affecter NMI et les prochaines écritures dans PPUSCROLL et PPUADDR. Ne l’insérez pas entre les deux octets d’une autre routine. Ces appels n’attendent pas VBlank et ne terminent pas les files de transfert en attente.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
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.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__scroll_latch_reset: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:112

Notes de déclaration (texte source)
Read PPUSTATUS to reset the scroll/address write latch; the read also clears VBlank status.

kitaqfc/kitaqfc/CodeGenerator.cs:4708

csharp
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

Mémorise les deux décalages en pixels dans les copies X/Y du runtime, lit PPUSTATUS pour réinitialiser le verrou, puis écrit X et Y dans PPUSCROLL, dans cet ordre. Met à jour le défilement directement, sans mettre un mouvement de caméra en file, attendre NMI ni choisir une table de noms de base.

Syntaxe

c
void __scroll_set(u8 x, u8 y);

Paramètres

x / y

Les décalages sont des octets non signés en pixels. Utilisez X 0..255 et normalement Y 0..239 ; Y 240..255 est écrit tel quel, mais suit un comportement particulier des lignes PPU. PPUCTRL fournit les bits de table de base. Rétablissez le défilement après l’accès direct à la VRAM et avant le rendu ; la fonction ne planifie pas ce moment.

Valeur renvoyée

Aucune (void).

Conditions et limites

Une lecture de PPUSTATUS peut affecter NMI et les prochaines écritures dans PPUSCROLL et PPUADDR. Ne l’insérez pas entre les deux octets d’une autre routine. Ces appels n’attendent pas VBlank et ne terminent pas les files de transfert en attente.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
__scroll_set(19,27); // Save both coordinates and write the two-scroll-byte pair.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__scroll_set: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:114

Notes de déclaration (texte source)
Save X/Y in runtime shadows, reset the PPU latch and write both scroll bytes immediately.

kitaqfc/kitaqfc/CodeGenerator.cs:4712

csharp
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

Remplace X mémorisé, réinitialise le verrou par une lecture d’état, puis écrit le nouveau X et Y mémorisé dans PPUSCROLL. Y provient de la copie logicielle, pas d’une lecture de la PPU ; les écritures directes du registre ne mettent pas cette copie à jour.

Syntaxe

c
void __scroll_x_set(u8 x);

Paramètres

x

Les décalages sont des octets non signés en pixels. Utilisez X 0..255 et normalement Y 0..239 ; Y 240..255 est écrit tel quel, mais suit un comportement particulier des lignes PPU. PPUCTRL fournit les bits de table de base. Rétablissez le défilement après l’accès direct à la VRAM et avant le rendu ; la fonction ne planifie pas ce moment.

Valeur renvoyée

Aucune (void).

Conditions et limites

Une lecture de PPUSTATUS peut affecter NMI et les prochaines écritures dans PPUSCROLL et PPUADDR. Ne l’insérez pas entre les deux octets d’une autre routine. Ces appels n’attendent pas VBlank et ne terminent pas les files de transfert en attente.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
__scroll_x_set(0); // Keeps saved Y=27 while changing X to zero.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__scroll_x_set: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:116

Notes de déclaration (texte source)
Replace saved X and write an X/Y pair using the retained Y shadow.

kitaqfc/kitaqfc/CodeGenerator.cs:4717

csharp
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

Remplace Y mémorisé, réinitialise le verrou par une lecture d’état, puis écrit X mémorisé et le nouveau Y dans PPUSCROLL. Les deux octets sont écrits même si seul Y change. X provient de la copie du runtime.

Syntaxe

c
void __scroll_y_set(u8 y);

Paramètres

y

Les décalages sont des octets non signés en pixels. Utilisez X 0..255 et normalement Y 0..239 ; Y 240..255 est écrit tel quel, mais suit un comportement particulier des lignes PPU. PPUCTRL fournit les bits de table de base. Rétablissez le défilement après l’accès direct à la VRAM et avant le rendu ; la fonction ne planifie pas ce moment.

Valeur renvoyée

Aucune (void).

Conditions et limites

Une lecture de PPUSTATUS peut affecter NMI et les prochaines écritures dans PPUSCROLL et PPUADDR. Ne l’insérez pas entre les deux octets d’une autre routine. Ces appels n’attendent pas VBlank et ne terminent pas les files de transfert en attente.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
__scroll_y_set(0); // Keeps saved X=0; the scene is now at the origin.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__scroll_y_set: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:118

Notes de déclaration (texte source)
Replace saved Y and write an X/Y pair using the retained X shadow.

kitaqfc/kitaqfc/CodeGenerator.cs:4722

csharp
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

Calcule un produit scalaire signé à deux termes.

Syntaxe

c
u16 __sdot2_q1_7(u16 a0, u16 a1, u8 b0, u8 b1);

Paramètres

a0

Motif de bits signé sur 16 bits représentant −32768 à 32767.

a1

Motif de bits signé sur 16 bits représentant −32768 à 32767.

b0

Motif de bits d’un octet signé, coefficient de −128 à 127.

b1

Motif de bits d’un octet signé, coefficient de −128 à 127.

Valeur renvoyée

Renvoie les 16 bits de poids faible de a0*b0+a1*b1.

Conditions et limites

Sur FC, les fonctions nommées q8_8/q1_7 utilisent les produits entiers bruts, sans décalage de mise à l’échelle fixe. Les fonctions Q1.7 sont des alias des opérations signées correspondantes. L’appelant gère les unités ; les résultats signés sont renvoyés comme motifs de bits u16.

Exemple d’utilisation

c
// 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);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Comparez les entrées littérales aux valeurs en RAM. Les résultats représentatifs sont affichés sur 16 bits non signés ; les négatifs utilisent le complément à deux. Le GB remet à l’échelle les produits à virgule fixe, tandis que le FC renvoie des produits entiers : des entrées identiques ne donnent donc pas nécessairement les mêmes résultats.

L’extrait stocke les valeurs suivantes sur 16 bits : result[34]=64959, result[35]=64959, result[36]=577, result[37]=577. Chaque calcul avec une entrée littérale correspond à son équivalent avec une entrée en RAM. L’écran affiche des valeurs représentatives ; le tableau result conserve tous les résultats.

__sdot2_q1_7: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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.chr
Implémentation et source

kitaqfc/lib/intrinsics.h:71

Notes de déclaration (texte source)
FC alias of __sdot2_q8_8: two raw signed-pattern products, summed without rescaling.

kitaqfc/kitaqfc/CodeGenerator.cs:4658

csharp
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

Calcule un produit scalaire signé à deux termes.

Syntaxe

c
u16 __sdot2_q8_8(u16 a0, u16 a1, u8 b0, u8 b1);

Paramètres

a0

Motif de bits signé sur 16 bits représentant −32768 à 32767.

a1

Motif de bits signé sur 16 bits représentant −32768 à 32767.

b0

Motif de bits d’un octet signé, coefficient de −128 à 127.

b1

Motif de bits d’un octet signé, coefficient de −128 à 127.

Valeur renvoyée

Renvoie les 16 bits de poids faible de a0*b0+a1*b1.

Conditions et limites

Sur FC, les fonctions nommées q8_8/q1_7 utilisent les produits entiers bruts, sans décalage de mise à l’échelle fixe. Les fonctions Q1.7 sont des alias des opérations signées correspondantes. L’appelant gère les unités ; les résultats signés sont renvoyés comme motifs de bits u16.

Exemple d’utilisation

c
// 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);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Comparez les entrées littérales aux valeurs en RAM. Les résultats représentatifs sont affichés sur 16 bits non signés ; les négatifs utilisent le complément à deux. Le GB remet à l’échelle les produits à virgule fixe, tandis que le FC renvoie des produits entiers : des entrées identiques ne donnent donc pas nécessairement les mêmes résultats.

L’extrait stocke les valeurs suivantes sur 16 bits : result[26]=64959, result[27]=64959, result[28]=577, result[29]=577. Chaque calcul avec une entrée littérale correspond à son équivalent avec une entrée en RAM. L’écran affiche des valeurs représentatives ; le tableau result conserve tous les résultats.

__sdot2_q8_8: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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.chr
Implémentation et source

kitaqfc/lib/intrinsics.h:67

Notes de déclaration (texte source)
Sum two signed-pattern products modulo 65536; no Q8.8 scaling shift is generated.

kitaqfc/kitaqfc/CodeGenerator.cs:4656

csharp
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

Calcule un produit scalaire signé à trois termes.

Syntaxe

c
u16 __sdot3_q1_7(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);

Paramètres

a0

Motif de bits signé sur 16 bits représentant −32768 à 32767.

a1

Motif de bits signé sur 16 bits représentant −32768 à 32767.

a2

Motif de bits signé sur 16 bits représentant −32768 à 32767.

b0

Motif de bits d’un octet signé, coefficient de −128 à 127.

b1

Motif de bits d’un octet signé, coefficient de −128 à 127.

b2

Motif de bits d’un octet signé, coefficient de −128 à 127.

Valeur renvoyée

Renvoie les 16 bits de poids faible de a0*b0+a1*b1+a2*b2.

Conditions et limites

Sur FC, les fonctions nommées q8_8/q1_7 utilisent les produits entiers bruts, sans décalage de mise à l’échelle fixe. Les fonctions Q1.7 sont des alias des opérations signées correspondantes. L’appelant gère les unités ; les résultats signés sont renvoyés comme motifs de bits u16.

Exemple d’utilisation

c
// 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);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Comparez les entrées littérales aux valeurs en RAM. Les résultats représentatifs sont affichés sur 16 bits non signés ; les négatifs utilisent le complément à deux. Le GB remet à l’échelle les produits à virgule fixe, tandis que le FC renvoie des produits entiers : des entrées identiques ne donnent donc pas nécessairement les mêmes résultats.

L’extrait stocke les valeurs suivantes sur 16 bits : result[38]=2498, result[39]=2498, result[40]=63038, result[41]=63038. Chaque calcul avec une entrée littérale correspond à son équivalent avec une entrée en RAM. L’écran affiche des valeurs représentatives ; le tableau result conserve tous les résultats.

__sdot3_q1_7: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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.chr
Implémentation et source

kitaqfc/lib/intrinsics.h:73

Notes de déclaration (texte source)
FC alias of __sdot3_q8_8: three raw signed-pattern products, summed without rescaling.

kitaqfc/kitaqfc/CodeGenerator.cs:4659

csharp
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

Calcule un produit scalaire signé à trois termes.

Syntaxe

c
u16 __sdot3_q8_8(u16 a0, u16 a1, u16 a2, u8 b0, u8 b1, u8 b2);

Paramètres

a0

Motif de bits signé sur 16 bits représentant −32768 à 32767.

a1

Motif de bits signé sur 16 bits représentant −32768 à 32767.

a2

Motif de bits signé sur 16 bits représentant −32768 à 32767.

b0

Motif de bits d’un octet signé, coefficient de −128 à 127.

b1

Motif de bits d’un octet signé, coefficient de −128 à 127.

b2

Motif de bits d’un octet signé, coefficient de −128 à 127.

Valeur renvoyée

Renvoie les 16 bits de poids faible de a0*b0+a1*b1+a2*b2.

Conditions et limites

Sur FC, les fonctions nommées q8_8/q1_7 utilisent les produits entiers bruts, sans décalage de mise à l’échelle fixe. Les fonctions Q1.7 sont des alias des opérations signées correspondantes. L’appelant gère les unités ; les résultats signés sont renvoyés comme motifs de bits u16.

Exemple d’utilisation

c
// 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);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Comparez les entrées littérales aux valeurs en RAM. Les résultats représentatifs sont affichés sur 16 bits non signés ; les négatifs utilisent le complément à deux. Le GB remet à l’échelle les produits à virgule fixe, tandis que le FC renvoie des produits entiers : des entrées identiques ne donnent donc pas nécessairement les mêmes résultats.

L’extrait stocke les valeurs suivantes sur 16 bits : result[30]=2498, result[31]=2498, result[32]=63038, result[33]=63038. Chaque calcul avec une entrée littérale correspond à son équivalent avec une entrée en RAM. L’écran affiche des valeurs représentatives ; le tableau result conserve tous les résultats.

__sdot3_q8_8: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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.chr
Implémentation et source

kitaqfc/lib/intrinsics.h:69

Notes de déclaration (texte source)
Sum three signed-pattern products modulo 65536; choose operand units explicitly in the caller.

kitaqfc/kitaqfc/CodeGenerator.cs:4657

csharp
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

Échantillonne une fois le bit 4 de $4017, utilisé comme ligne de réception d’un adaptateur externe.

Syntaxe

c
u8 __serial_rx_bit(void);

Valeur renvoyée

1 si le bit 4 est à 1, sinon 0. Ce n’est ni un indicateur de données disponibles ni un état d’erreur de communication.

Conditions et limites

Ces API accèdent directement à $4016/$4017. N’entrelacez pas sur les mêmes ports des opérations de manette, clavier ou série pilotées par interruption. Les positions de décalage des contrôleurs peuvent aussi être affectées. L’exemple désactive la NMI pendant la capture des entrées, puis l’active à l’initialisation de l’affichage.

Ces primitives supposent qu’un adaptateur externe relie OUT0 à TX et $4017 D4 à RX. Elles ne gèrent ni intervalle entre bits, ni horloge de transmission, ni bits de début et de fin, ni assemblage des octets, ni attente de réception. Leur rôle diffère de celui des fonctions par octet telles que __midi_out().

L’appelant doit effectuer la lecture au bon moment. La fonction n’attend pas de données, n’assemble pas plusieurs bits et ne modifie pas la sortie $4016.

La conversion des bits est aussi vérifiée dans une ROM de test créée pour ce projet, en redirigeant les lectures de registres vers des valeurs de test en RAM. Les cas couvrent les combinaisons 00/08/10/18 avec d’autres bits à 1. Ce test unitaire est distinct de l’exécution de l’exemple non modifié et du comportement d’un périphérique réel.

Ces images exécutent les ROM téléchargeables sans modification dans KUROSAKI. Son bus d’entrée modélise les manettes, mais pas les signaux du clavier, du pistolet optique ou d’un adaptateur série. Les bits d’entrée externes se lisent à zéro ; les captures ne valident donc pas ces appareils. Les touches physiques, la détection optique et la communication restent non testées.

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Pour VALUE 000/001/002/003/254/255, EXPECTED OUT0 vaut 0/1/0/1/0/1. Cette colonne explique la sélection du bit de poids faible ; elle ne constitue pas une relecture du signal électrique. Les traces CPU confirment ces écritures, suivies d’une dernière écriture de 1. RX SAMPLE vaut 000, car KUROSAKI n’émule pas la ligne de réception.

__serial_rx_bit: SERIAL-DEFAULT — Pour VALUE 000/001/002/003/254/255, EXPECTED OUT0 vaut 0/1/0/1/0/1. Cette colonne explique la sélection du bit de poids faible ; elle ne constitue pas une relecture du signal électrique. Les traces CPU confirment ces écritures, suivies d’une dernière écriture de 1. RX SAMPLE vaut 000, car KUROSAKI n’émule pas la ligne de réception.
SERIAL-DEFAULT — Pour VALUE 000/001/002/003/254/255, EXPECTED OUT0 vaut 0/1/0/1/0/1. Cette colonne explique la sélection du bit de poids faible ; elle ne constitue pas une relecture du signal électrique. Les traces CPU confirment ces écritures, suivies d’une dernière écriture de 1. RX SAMPLE vaut 000, car KUROSAKI n’émule pas la ligne de réception.

Programme d’exemple complet

Commande de compilation

powershell
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.png
Implémentation et source

kitaqfc/lib/intrinsics.h:288

Notes de déclaration (texte source)
Read 4017 bit 4 and normalize it to 0 or 1; an external serial adapter supplies the signal.

kitaqfc/kitaqfc/CodeGenerator.cs:7965

csharp
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

Écrit le bit de poids faible de l’argument sur OUT0 via $4016, pour fournir une sortie d’un bit à un adaptateur série externe.

Syntaxe

c
void __serial_tx_bit(u8 bit);

Paramètres

bit

Valeur à émettre. Seul bit & 1 est utilisé : 2 émet zéro, tandis que 255 émet un.

Valeur renvoyée

Aucune valeur de retour.

Conditions et limites

Ces API accèdent directement à $4016/$4017. N’entrelacez pas sur les mêmes ports des opérations de manette, clavier ou série pilotées par interruption. Les positions de décalage des contrôleurs peuvent aussi être affectées. L’exemple désactive la NMI pendant la capture des entrées, puis l’active à l’initialisation de l’affichage.

Ces primitives supposent qu’un adaptateur externe relie OUT0 à TX et $4017 D4 à RX. Elles ne gèrent ni intervalle entre bits, ni horloge de transmission, ni bits de début et de fin, ni assemblage des octets, ni attente de réception. Leur rôle diffère de celui des fonctions par octet telles que __midi_out().

Écrit zéro ou un dans l’ensemble de $4016, ce qui remet OUT1/OUT2 à zéro. Aucun délai de maintien n’est ajouté. OUT0 commande le verrouillage des boutons de la manette standard ; cette opération modifie donc aussi l’état de lecture de la manette.

Ces images exécutent les ROM téléchargeables sans modification dans KUROSAKI. Son bus d’entrée modélise les manettes, mais pas les signaux du clavier, du pistolet optique ou d’un adaptateur série. Les bits d’entrée externes se lisent à zéro ; les captures ne valident donc pas ces appareils. Les touches physiques, la détection optique et la communication restent non testées.

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Pour VALUE 000/001/002/003/254/255, EXPECTED OUT0 vaut 0/1/0/1/0/1. Cette colonne explique la sélection du bit de poids faible ; elle ne constitue pas une relecture du signal électrique. Les traces CPU confirment ces écritures, suivies d’une dernière écriture de 1. RX SAMPLE vaut 000, car KUROSAKI n’émule pas la ligne de réception.

__serial_tx_bit: SERIAL-DEFAULT — Pour VALUE 000/001/002/003/254/255, EXPECTED OUT0 vaut 0/1/0/1/0/1. Cette colonne explique la sélection du bit de poids faible ; elle ne constitue pas une relecture du signal électrique. Les traces CPU confirment ces écritures, suivies d’une dernière écriture de 1. RX SAMPLE vaut 000, car KUROSAKI n’émule pas la ligne de réception.
SERIAL-DEFAULT — Pour VALUE 000/001/002/003/254/255, EXPECTED OUT0 vaut 0/1/0/1/0/1. Cette colonne explique la sélection du bit de poids faible ; elle ne constitue pas une relecture du signal électrique. Les traces CPU confirment ces écritures, suivies d’une dernière écriture de 1. RX SAMPLE vaut 000, car KUROSAKI n’émule pas la ligne de réception.

Programme d’exemple complet

Commande de compilation

powershell
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.png
Implémentation et source

kitaqfc/lib/intrinsics.h:286

Notes de déclaration (texte source)
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

csharp
EmitHelperStart("__serial_tx_bit");
            EmitAsm("LDA", Mem(CallArgBase));
            EmitAsm("AND", Imm(1));
            EmitAsm("STA", Mem(0x4016));
            EmitAsm("RTS");
__smac16

Calcule une multiplication-accumulation signée.

Syntaxe

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

Paramètres

acc

Accumulateur sur 16 bits à ajouter.

a

Motif de bits signé sur 16 bits représentant −32768 à 32767.

b

Motif de bits d’un octet signé, coefficient de −128 à 127.

Valeur renvoyée

Renvoie les 16 bits de poids faible de acc+a*b.

Conditions et limites

Sur FC, les fonctions nommées q8_8/q1_7 utilisent les produits entiers bruts, sans décalage de mise à l’échelle fixe. Les fonctions Q1.7 sont des alias des opérations signées correspondantes. L’appelant gère les unités ; les résultats signés sont renvoyés comme motifs de bits u16.

Exemple d’utilisation

c
// 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);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Comparez les entrées littérales aux valeurs en RAM. Les résultats représentatifs sont affichés sur 16 bits non signés ; les négatifs utilisent le complément à deux. Le GB remet à l’échelle les produits à virgule fixe, tandis que le FC renvoie des produits entiers : des entrées identiques ne donnent donc pas nécessairement les mêmes résultats.

L’extrait stocke les valeurs suivantes sur 16 bits : result[10]=32796, result[11]=32796, result[12]=32668, result[13]=32668. Chaque calcul avec une entrée littérale correspond à son équivalent avec une entrée en RAM. L’écran affiche des valeurs représentatives ; le tableau result conserve tous les résultats.

__smac16: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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.chr
Implémentation et source

kitaqfc/lib/intrinsics.h:57

Notes de déclaration (texte source)
Add the signed-pattern product to acc modulo 65536; the API carries the result as u16 bits.

kitaqfc/kitaqfc/CodeGenerator.cs:4651

csharp
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

Calcule une multiplication-accumulation signée.

Syntaxe

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

Paramètres

acc

Accumulateur sur 16 bits à ajouter.

a

Motif de bits signé sur 16 bits représentant −32768 à 32767.

b

Motif de bits d’un octet signé, coefficient de −128 à 127.

Valeur renvoyée

Renvoie les 16 bits de poids faible de acc+a*b.

Conditions et limites

Sur FC, les fonctions nommées q8_8/q1_7 utilisent les produits entiers bruts, sans décalage de mise à l’échelle fixe. Les fonctions Q1.7 sont des alias des opérations signées correspondantes. L’appelant gère les unités ; les résultats signés sont renvoyés comme motifs de bits u16.

Exemple d’utilisation

c
// 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);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Comparez les entrées littérales aux valeurs en RAM. Les résultats représentatifs sont affichés sur 16 bits non signés ; les négatifs utilisent le complément à deux. Le GB remet à l’échelle les produits à virgule fixe, tandis que le FC renvoie des produits entiers : des entrées identiques ne donnent donc pas nécessairement les mêmes résultats.

L’extrait stocke les valeurs suivantes sur 16 bits : result[18]=32796, result[19]=32796, result[20]=32668, result[21]=32668. Chaque calcul avec une entrée littérale correspond à son équivalent avec une entrée en RAM. L’écran affiche des valeurs représentatives ; le tableau result conserve tous les résultats.

__smac16_q1_7: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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.chr
Implémentation et source

kitaqfc/lib/intrinsics.h:61

Notes de déclaration (texte source)
FC alias of __smac16: accumulate a raw signed-pattern product modulo 65536.

kitaqfc/kitaqfc/CodeGenerator.cs:4653

csharp
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

Calcule une multiplication signée.

Syntaxe

c
u16 __smul16x8(u16 a, u8 b);

Paramètres

a

Motif de bits signé sur 16 bits représentant −32768 à 32767.

b

Motif de bits d’un octet signé, coefficient de −128 à 127.

Valeur renvoyée

Renvoie les 16 bits de poids faible de a*b.

Conditions et limites

Sur FC, les fonctions nommées q8_8/q1_7 utilisent les produits entiers bruts, sans décalage de mise à l’échelle fixe. Les fonctions Q1.7 sont des alias des opérations signées correspondantes. L’appelant gère les unités ; les résultats signés sont renvoyés comme motifs de bits u16.

Exemple d’utilisation

c
// 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);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Comparez les entrées littérales aux valeurs en RAM. Les résultats représentatifs sont affichés sur 16 bits non signés ; les négatifs utilisent le complément à deux. Le GB remet à l’échelle les produits à virgule fixe, tandis que le FC renvoie des produits entiers : des entrées identiques ne donnent donc pas nécessairement les mêmes résultats.

L’extrait stocke les valeurs suivantes sur 16 bits : result[4]=32832, result[5]=32832, result[6]=32704, result[7]=32704. Chaque calcul avec une entrée littérale correspond à son équivalent avec une entrée en RAM. L’écran affiche des valeurs représentatives ; le tableau result conserve tous les résultats.

__smul16x8: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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.chr
Implémentation et source

kitaqfc/lib/intrinsics.h:53

Notes de déclaration (texte source)
Interpret a and b as signed two's-complement bit patterns and return the low word of their product.

kitaqfc/kitaqfc/CodeGenerator.cs:4648

csharp
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

Calcule une multiplication signée.

Syntaxe

c
u16 __smul16x8_q1_7(u16 a, u8 b);

Paramètres

a

Motif de bits signé sur 16 bits représentant −32768 à 32767.

b

Motif de bits d’un octet signé, coefficient de −128 à 127.

Valeur renvoyée

Renvoie les 16 bits de poids faible de a*b.

Conditions et limites

Sur FC, les fonctions nommées q8_8/q1_7 utilisent les produits entiers bruts, sans décalage de mise à l’échelle fixe. Les fonctions Q1.7 sont des alias des opérations signées correspondantes. L’appelant gère les unités ; les résultats signés sont renvoyés comme motifs de bits u16.

Exemple d’utilisation

c
// 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);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Comparez les entrées littérales aux valeurs en RAM. Les résultats représentatifs sont affichés sur 16 bits non signés ; les négatifs utilisent le complément à deux. Le GB remet à l’échelle les produits à virgule fixe, tandis que le FC renvoie des produits entiers : des entrées identiques ne donnent donc pas nécessairement les mêmes résultats.

L’extrait stocke les valeurs suivantes sur 16 bits : result[14]=32832, result[15]=32832, result[16]=32704, result[17]=32704. Chaque calcul avec une entrée littérale correspond à son équivalent avec une entrée en RAM. L’écran affiche des valeurs représentatives ; le tableau result conserve tous les résultats.

__smul16x8_q1_7: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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.chr
Implémentation et source

kitaqfc/lib/intrinsics.h:59

Notes de déclaration (texte source)
FC alias of __smul16x8: signed-pattern raw product, with no Q1.7 scaling shift.

kitaqfc/kitaqfc/CodeGenerator.cs:4652

csharp
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

Attend un nouveau hit du sprite zéro puis écrit X/Y de défilement pour une séparation horizontale, gardant le haut aligné et décalant le fond inférieur.

Syntaxe

c
void __split_scroll_sprite0(u8 x, u8 y);

Paramètres

x

Défilement horizontal de 0 à 255, pas la position X d’un objet. L’exemple utilise 32 pour décaler de 32 pixels vers la gauche la partie inférieure du même fond.

y

Réglage temporaire de défilement vertical de 0 à 255 ; la plage visible habituelle est 0..239. La fonction écrit $2005 et ne change donc pas immédiatement la position verticale active pendant le rendu. L’exemple de séparation horizontale passe zéro.

Valeur renvoyée

Aucune valeur de retour.

Conditions et limites

Faites chevaucher un pixel opaque du sprite OAM 0 et un pixel opaque du fond, les deux couches activées. La priorité derrière le fond permet toujours un hit. Les huit pixels de gauche nécessitent les autorisations de couche correspondantes ; un chevauchement en X=255 ne déclenche pas de hit.

Interroge le bit 6 de PPUSTATUS ($2002) jusqu’à son effacement, puis jusqu’à son activation. Si un hit a déjà eu lieu à l’appel, attend un nouveau hit à l’image suivante. Lire le drapeau ne l’acquitte pas ; le PPU l’efface pendant le pré-rendu.

Aucun délai d’expiration ni retour d’échec. Sprite 0 caché, fond transparent, rendu désactivé ou toute absence de chevauchement bloque l’appel. Assurez un hit avant d’appeler. L’activation IRQ CPU n’est pas nécessaire et la fonction n’active pas NMI. L’exemple active NMI pour son attente d’image distincte.

Lire $2002 affecte aussi le verrou partagé des écritures d’adresse/défilement et le drapeau VBlank. N’insérez pas cet appel entre les deux écritures d’une opération $2005/$2006. Utilisez-le depuis le code principal, pas un gestionnaire NMI/IRQ.

Après le hit, lit $2002 puis écrit X et Y dans $2005. X fin change immédiatement ; X grossier au prochain rechargement horizontal. Y s’applique normalement au prochain rechargement de pré-rendu, sauf remplacement préalable par une autre écriture. Ce n’est pas un repositionnement raster X/Y arbitraire immédiat.

L’interrogation et l’exécution des instructions retardent les écritures. KUROSAKI rend par ligne ; ces captures ne prouvent donc pas une frontière matérielle exacte au point près. L’exemple place le changement dans la bande blanche Y=96..103, loin des barres bleues et rouges.

L’en-tête partagé fc_sprite0_scene.h prépare des barres originales bleues/rouges et une bande blanche horizontale. Le sprite 0 est en (16,96), caché derrière le fond ; son Y OAM brut vaut 95 car Y affiché=Y OAM+1. Chaque image rétablit le défilement supérieur à zéro en VBlank avant d’attendre le hit.

NESdev : défilement du PPU

NESdev : référence PPU pour les programmeurs

Exemple d’utilisation

c
void main(void) {
    s0_prepare();
    while (1) {
        s0_frame();
        __split_scroll_sprite0(32,0);
        s0_completed++;
    }
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Au-dessus de la bande blanche, bleu en X=64..95 et rouge en X=160..191. En dessous, dès Y=104, ils passent à X=32..63 et X=128..159 : seule la zone inférieure se décale de 32 pixels vers la gauche. La bande est en Y=96..103 et les barres finissent à Y=207. La validation vérifie une terminaison par image et la position/couleur de chaque pixel.

__split_scroll_sprite0: SPLIT-DEFAULT — Au-dessus de la bande blanche, bleu en X=64..95 et rouge en X=160..191. En dessous, dès Y=104, ils passent à X=32..63 et X=128..159 : seule la zone inférieure se décale de 32 pixels vers la gauche. La bande est en Y=96..103 et les barres finissent à Y=207. La validation vérifie une terminaison par image et la position/couleur de chaque pixel.
SPLIT-DEFAULT — Au-dessus de la bande blanche, bleu en X=64..95 et rouge en X=160..191. En dessous, dès Y=104, ils passent à X=32..63 et X=128..159 : seule la zone inférieure se décale de 32 pixels vers la gauche. La bande est en Y=96..103 et les barres finissent à Y=207. La validation vérifie une terminaison par image et la position/couleur de chaque pixel.

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:336

Notes de déclaration (texte source)
Wait for a new sprite-zero hit, reset the PPU latch and write X/Y scroll; requires a visible hit setup.
__sprite0_wait_hit

Attend un nouveau chevauchement sprite zéro/fond pour synchroniser le code principal avec une région connue de l’écran, sans modifier le défilement.

Syntaxe

c
void __sprite0_wait_hit(void);

Valeur renvoyée

Aucune valeur de retour.

Conditions et limites

Faites chevaucher un pixel opaque du sprite OAM 0 et un pixel opaque du fond, les deux couches activées. La priorité derrière le fond permet toujours un hit. Les huit pixels de gauche nécessitent les autorisations de couche correspondantes ; un chevauchement en X=255 ne déclenche pas de hit.

Interroge le bit 6 de PPUSTATUS ($2002) jusqu’à son effacement, puis jusqu’à son activation. Si un hit a déjà eu lieu à l’appel, attend un nouveau hit à l’image suivante. Lire le drapeau ne l’acquitte pas ; le PPU l’efface pendant le pré-rendu.

Aucun délai d’expiration ni retour d’échec. Sprite 0 caché, fond transparent, rendu désactivé ou toute absence de chevauchement bloque l’appel. Assurez un hit avant d’appeler. L’activation IRQ CPU n’est pas nécessaire et la fonction n’active pas NMI. L’exemple active NMI pour son attente d’image distincte.

Lire $2002 affecte aussi le verrou partagé des écritures d’adresse/défilement et le drapeau VBlank. N’insérez pas cet appel entre les deux écritures d’une opération $2005/$2006. Utilisez-le depuis le code principal, pas un gestionnaire NMI/IRQ.

L’interrogation et l’exécution des instructions retardent les écritures. KUROSAKI rend par ligne ; ces captures ne prouvent donc pas une frontière matérielle exacte au point près. L’exemple place le changement dans la bande blanche Y=96..103, loin des barres bleues et rouges.

L’en-tête partagé fc_sprite0_scene.h prépare des barres originales bleues/rouges et une bande blanche horizontale. Le sprite 0 est en (16,96), caché derrière le fond ; son Y OAM brut vaut 95 car Y affiché=Y OAM+1. Chaque image rétablit le défilement supérieur à zéro en VBlank avant d’attendre le hit.

NESdev : défilement du PPU

NESdev : référence PPU pour les programmeurs

Exemple d’utilisation

c
void main(void) {
    s0_prepare();
    while (1) {
        s0_frame();
        __sprite0_wait_hit();
        s0_completed++;
    }
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

La barre bleue occupe X=64..95, la rouge X=160..191, alignées de part et d’autre de la bande blanche. Attendre seul ne change pas le défilement. L’exemple exécuté termine 116 attentes après 120 images et 117 après 121, confirmant sa progression.

__sprite0_wait_hit: WAIT-DEFAULT — La barre bleue occupe X=64..95, la rouge X=160..191, alignées de part et d’autre de la bande blanche. Attendre seul ne change pas le défilement. L’exemple exécuté termine 116 attentes après 120 images et 117 après 121, confirmant sa progression.
WAIT-DEFAULT — La barre bleue occupe X=64..95, la rouge X=160..191, alignées de part et d’autre de la bande blanche. Attendre seul ne change pas le défilement. L’exemple exécuté termine 116 attentes après 120 images et 117 après 121, confirmant sa progression.

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:334

Notes de déclaration (texte source)
Wait for the current hit flag to clear, then for a new hit. No timeout is provided.
__sprite_attr

Remplace l’octet d’attributs complet d’un sprite du tampon miroir. Il permet de choisir la palette, la priorité par rapport au fond et les retournements horizontal ou vertical sans changer la tuile ni la position.

Syntaxe

c
void __sprite_attr(u8 index, u8 attr);

Paramètres

index

Indice du sprite, de 0 à 63, et non décalage en octets. Le calcul multiplie cet indice par quatre dans un curseur de 8 bits ; les valeurs hors plage sont ramenées modulo 64 et peuvent écraser une autre entrée. L’allocation n’est pas vérifiée.

attr

Octet d’attributs complet : les bits 0–1 choisissent la palette 0–3 ; le bit 5 place les pixels opaques du sprite derrière ceux du fond ; les bits 6 et 7 retournent le dessin horizontalement et verticalement. Laissez les bits 2–4 à zéro. Combinez les indicateurs par OU bit à bit ; l’octet remplace tous les attributs précédents.

Valeur renvoyée

Aucune (void).

Conditions et limites

Ces opérations modifient la RAM CPU 0x0200–0x02FF, sans mettre immédiatement à jour l’OAM matérielle. Transférez la page complète avec __oam_dma. Elles ne tiennent à jour ni les indicateurs d’allocation, ni les tableaux de coordonnées, ni le compteur de la bibliothèque de sprites. Évitez qu’une interruption modifie ou transfère une page encore en préparation.

Exemple d’utilisation

c
__sprite_attr(3,0xC0); // Flip the arrow horizontally and vertically.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple réserve une ligne légendée à chaque opération. SET et MOVE montrent des flèches de 8×8 vers la droite en (144,16) et (144,32) ; TILE, un carré vert en (144,48) ; ATTR, la flèche retournée selon les deux axes en (144,64). HIDE en (144,80) et CLEAR en (144,112) doivent rester vides. META montre une paire en (136,96) et (144,96), la seconde flèche étant retournée horizontalement. NEXT 07 indique l’indice suivant après les entrées 5 et 6. Les bords sont rouges, la tige verte et les détails de la pointe bleus. La vérification compare 8 704 pixels par image et les octets OAM transférés ; le matériel physique n’a pas été testé.

TRANSFER montre une flèche en (144,128). Ce programme envoie directement la page 02 avec __oam_dma, y compris tous les champs préparés par les opérations individuelles sur les sprites.

__sprite_attr: PAGE02
PAGE02

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:198

Notes de déclaration (texte source)
Replace the selected shadow attribute byte, including palette, priority and flip bits.
csharp
EmitHelperStart("__sprite_attr");
            EmitSpriteIndexToX();
            EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("STA", new AsmOperand(OamRamBase + 2, AddressMode.AbsoluteX));
            EmitAsm("RTS");
__sprite_hide

Masque un sprite du tampon miroir en réglant Y sur 0xF0. X, la tuile et les attributs restent disponibles pour sa réutilisation. La fonction ne libère aucune allocation et ne modifie pas le compteur de sprites actifs de la bibliothèque.

Syntaxe

c
void __sprite_hide(u8 index);

Paramètres

index

Indice du sprite, de 0 à 63, et non décalage en octets. Le calcul multiplie cet indice par quatre dans un curseur de 8 bits ; les valeurs hors plage sont ramenées modulo 64 et peuvent écraser une autre entrée. L’allocation n’est pas vérifiée.

Valeur renvoyée

Aucune (void).

Conditions et limites

Ces opérations modifient la RAM CPU 0x0200–0x02FF, sans mettre immédiatement à jour l’OAM matérielle. Transférez la page complète avec __oam_dma. Elles ne tiennent à jour ni les indicateurs d’allocation, ni les tableaux de coordonnées, ni le compteur de la bibliothèque de sprites. Évitez qu’une interruption modifie ou transfère une page encore en préparation.

Exemple d’utilisation

c
__sprite_hide(4); // The HIDE row remains empty after DMA.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple réserve une ligne légendée à chaque opération. SET et MOVE montrent des flèches de 8×8 vers la droite en (144,16) et (144,32) ; TILE, un carré vert en (144,48) ; ATTR, la flèche retournée selon les deux axes en (144,64). HIDE en (144,80) et CLEAR en (144,112) doivent rester vides. META montre une paire en (136,96) et (144,96), la seconde flèche étant retournée horizontalement. NEXT 07 indique l’indice suivant après les entrées 5 et 6. Les bords sont rouges, la tige verte et les détails de la pointe bleus. La vérification compare 8 704 pixels par image et les octets OAM transférés ; le matériel physique n’a pas été testé.

TRANSFER montre une flèche en (144,128). Ce programme envoie directement la page 02 avec __oam_dma, y compris tous les champs préparés par les opérations individuelles sur les sprites.

__sprite_hide: PAGE02
PAGE02

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:200

Notes de déclaration (texte source)
Write F0 to the selected shadow Y byte; issue DMA to make the change visible.
csharp
EmitHelperStart("__sprite_hide");
            EmitSpriteIndexToX();
            EmitAsm("LDA", Imm(0xF0)); EmitAsm("STA", new AsmOperand(OamRamBase + 0, AddressMode.AbsoluteX));
            EmitAsm("RTS");
__sprite_move

Modifie uniquement les octets X et Y d’un sprite du tampon miroir. La tuile et les attributs sont conservés : le même dessin se déplace sans changer de palette ni de retournement.

Syntaxe

c
void __sprite_move(u8 index, u8 x, u8 y);

Paramètres

index

Indice du sprite, de 0 à 63, et non décalage en octets. Le calcul multiplie cet indice par quatre dans un curseur de 8 bits ; les valeurs hors plage sont ramenées modulo 64 et peuvent écraser une autre entrée. L’allocation n’est pas vérifiée.

x

Position horizontale en pixels, sous forme d’octet non signé ; ce n’est pas une coordonnée de tuile.

y

Octet Y de l’OAM. Pour placer le haut sous la ligne zéro, passez sa position moins un ; 239..255 sont hors des 240 lignes visibles.

Valeur renvoyée

Aucune (void).

Conditions et limites

Ces opérations modifient la RAM CPU 0x0200–0x02FF, sans mettre immédiatement à jour l’OAM matérielle. Transférez la page complète avec __oam_dma. Elles ne tiennent à jour ni les indicateurs d’allocation, ni les tableaux de coordonnées, ni le compteur de la bibliothèque de sprites. Évitez qu’une interruption modifie ou transfère une page encore en préparation.

Exemple d’utilisation

c
__sprite_move(1,144,31); // Move to (144,32), retaining tile 128 and attributes 0.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple réserve une ligne légendée à chaque opération. SET et MOVE montrent des flèches de 8×8 vers la droite en (144,16) et (144,32) ; TILE, un carré vert en (144,48) ; ATTR, la flèche retournée selon les deux axes en (144,64). HIDE en (144,80) et CLEAR en (144,112) doivent rester vides. META montre une paire en (136,96) et (144,96), la seconde flèche étant retournée horizontalement. NEXT 07 indique l’indice suivant après les entrées 5 et 6. Les bords sont rouges, la tige verte et les détails de la pointe bleus. La vérification compare 8 704 pixels par image et les octets OAM transférés ; le matériel physique n’a pas été testé.

TRANSFER montre une flèche en (144,128). Ce programme envoie directement la page 02 avec __oam_dma, y compris tous les champs préparés par les opérations individuelles sur les sprites.

__sprite_move: PAGE02
PAGE02

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:194

Notes de déclaration (texte source)
Replace only shadow X/Y for index 0..63; tile and attribute bytes remain unchanged.
csharp
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

Écrit les quatre champs d’un sprite dans le tampon miroir de la page 02. Les arguments sont l’indice, X, Y, la tuile et les attributs ; l’ordre en mémoire est Y, tuile, attributs, X.

Syntaxe

c
void __sprite_set(u8 index, u8 x, u8 y, u8 tile, u8 attr);

Paramètres

index

Indice du sprite, de 0 à 63, et non décalage en octets. Le calcul multiplie cet indice par quatre dans un curseur de 8 bits ; les valeurs hors plage sont ramenées modulo 64 et peuvent écraser une autre entrée. L’allocation n’est pas vérifiée.

x

Position horizontale en pixels, sous forme d’octet non signé ; ce n’est pas une coordonnée de tuile.

y

Octet Y de l’OAM. Pour placer le haut sous la ligne zéro, passez sa position moins un ; 239..255 sont hors des 240 lignes visibles.

tile

Numéro de tuile sur 8 bits. Chargez d’abord les graphismes. Les règles matérielles du mode 8×16 s’appliquent ; aucun ajustement pour les paires de tuiles n’est effectué.

attr

Octet d’attributs complet : les bits 0–1 choisissent la palette 0–3 ; le bit 5 place les pixels opaques du sprite derrière ceux du fond ; les bits 6 et 7 retournent le dessin horizontalement et verticalement. Laissez les bits 2–4 à zéro. Combinez les indicateurs par OU bit à bit ; l’octet remplace tous les attributs précédents.

Valeur renvoyée

Aucune (void).

Conditions et limites

Ces opérations modifient la RAM CPU 0x0200–0x02FF, sans mettre immédiatement à jour l’OAM matérielle. Transférez la page complète avec __oam_dma. Elles ne tiennent à jour ni les indicateurs d’allocation, ni les tableaux de coordonnées, ni le compteur de la bibliothèque de sprites. Évitez qu’une interruption modifie ou transfère une page encore en préparation.

Exemple d’utilisation

c
__sprite_set(0,144,15,128,0); // An 8x8 arrow at visible position (144,16).

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple réserve une ligne légendée à chaque opération. SET et MOVE montrent des flèches de 8×8 vers la droite en (144,16) et (144,32) ; TILE, un carré vert en (144,48) ; ATTR, la flèche retournée selon les deux axes en (144,64). HIDE en (144,80) et CLEAR en (144,112) doivent rester vides. META montre une paire en (136,96) et (144,96), la seconde flèche étant retournée horizontalement. NEXT 07 indique l’indice suivant après les entrées 5 et 6. Les bords sont rouges, la tige verte et les détails de la pointe bleus. La vérification compare 8 704 pixels par image et les octets OAM transférés ; le matériel physique n’a pas été testé.

TRANSFER montre une flèche en (144,128). Ce programme envoie directement la page 02 avec __oam_dma, y compris tous les champs préparés par les opérations individuelles sur les sprites.

__sprite_set: PAGE02
PAGE02

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:192

Notes de déclaration (texte source)
Write Y, tile, attributes and X to one shadow entry. Use indices 0..63; coordinates are raw OAM bytes.
csharp
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

Remplace uniquement le numéro de tuile d’un sprite du tampon miroir, sans modifier sa position ni ses attributs. Cette opération sélectionne un motif existant ; elle ne charge pas de graphismes en mémoire CHR.

Syntaxe

c
void __sprite_tile(u8 index, u8 tile);

Paramètres

index

Indice du sprite, de 0 à 63, et non décalage en octets. Le calcul multiplie cet indice par quatre dans un curseur de 8 bits ; les valeurs hors plage sont ramenées modulo 64 et peuvent écraser une autre entrée. L’allocation n’est pas vérifiée.

tile

Numéro de tuile sur 8 bits. Chargez d’abord les graphismes. Les règles matérielles du mode 8×16 s’appliquent ; aucun ajustement pour les paires de tuiles n’est effectué.

Valeur renvoyée

Aucune (void).

Conditions et limites

Ces opérations modifient la RAM CPU 0x0200–0x02FF, sans mettre immédiatement à jour l’OAM matérielle. Transférez la page complète avec __oam_dma. Elles ne tiennent à jour ni les indicateurs d’allocation, ni les tableaux de coordonnées, ni le compteur de la bibliothèque de sprites. Évitez qu’une interruption modifie ou transfère une page encore en préparation.

Exemple d’utilisation

c
__sprite_tile(2,129); // Select the green square at (144,48).

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

L’exemple réserve une ligne légendée à chaque opération. SET et MOVE montrent des flèches de 8×8 vers la droite en (144,16) et (144,32) ; TILE, un carré vert en (144,48) ; ATTR, la flèche retournée selon les deux axes en (144,64). HIDE en (144,80) et CLEAR en (144,112) doivent rester vides. META montre une paire en (136,96) et (144,96), la seconde flèche étant retournée horizontalement. NEXT 07 indique l’indice suivant après les entrées 5 et 6. Les bords sont rouges, la tige verte et les détails de la pointe bleus. La vérification compare 8 704 pixels par image et les octets OAM transférés ; le matériel physique n’a pas été testé.

TRANSFER montre une flèche en (144,128). Ce programme envoie directement la page 02 avec __oam_dma, y compris tous les champs préparés par les opérations individuelles sur les sprites.

__sprite_tile: PAGE02
PAGE02

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:196

Notes de déclaration (texte source)
Replace the selected shadow tile byte; this does not upload tile patterns.
csharp
EmitHelperStart("__sprite_tile");
            EmitSpriteIndexToX();
            EmitAsm("LDA", Mem(CallArgBase + 1)); EmitAsm("STA", new AsmOperand(OamRamBase + 1, AddressMode.AbsoluteX));
            EmitAsm("RTS");
__vram_fill

Définit ppu_addr, puis écrit le même octet value dans PPUDATA exactement len fois. Ce remplissage direct est terminé au retour. Il ne tient compte ni des dimensions des tuiles, ni des attributs, ni des limites d’un rectangle.

Syntaxe

c
void __vram_fill(u16 ppu_addr, u8 value, u8 len);

Paramètres

ppu_addr

Adresse PPU en octets (0x0000–0x3FFF), et non pointeur CPU. La destination doit être inscriptible : modifier les motifs exige de la CHR RAM, pas de la CHR ROM. Le mappage et le mirroring PPU s’appliquent. Pour les copies et remplissages contigus, choisissez l’incrément 1 dans PPUCTRL. Exécutez à un instant sûr, puis rétablissez le défilement voulu ; l’exécuteur écrit PPUADDR et PPUDATA sans restaurer le défilement.

value

Un octet de 0 à 255 répété. Son effet visible dépend du contenu de destination : indices de tuiles, plans de pixels ou autre structure d’octets.

len

Nombre d’octets de 0..255. Zéro définit aussi l’adresse PPU et réinitialise le verrou, sans écrire de données. Aucun contrôle de capacité, découpage, changement de banque ou transfert différé n’est prévu.

Valeur renvoyée

Aucune (void).

Conditions et limites

L’accès direct à la PPU exige de désactiver le rendu ou de terminer toute l’opération dans un intervalle VBlank sûr. Ces fonctions n’attendent pas, ne mettent rien en file, ne bloquent pas NMI et ne rétablissent pas le défilement. Empêchez une interruption de modifier le verrou ou l’adresse partagés pendant l’appel. Le bit 2 de PPUCTRL choisit un pas de destination de 1 ou 32 par octet. La PPU gère les miroirs et le bouclage des adresses ; la CHR-ROM n’est pas inscriptible. Rétablissez le défilement voulu avant le rendu.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
__vram_fill(0x2060,2,32); // A second blue row of 4-pixel-wide left halves.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__vram_fill: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:122

Notes de déclaration (texte source)
Write repeated bytes directly; this is not an enqueue operation.

kitaqfc/kitaqfc/CodeGenerator.cs:4818

csharp
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

Réinitialise le verrou PPU, définit ppu_addr et copie de façon synchrone len octets consécutifs de la source vers PPUDATA. Le nombre est sur huit bits et le compilateur utilise des lectures indexées. Aucun pointeur n’est conservé pour plus tard ; modifier la source après le retour ne change pas la VRAM.

Syntaxe

c
void __vram_write(u16 ppu_addr, const u8* src, u8 len);

Paramètres

ppu_addr

Adresse PPU en octets (0x0000–0x3FFF), et non pointeur CPU. La destination doit être inscriptible : modifier les motifs exige de la CHR RAM, pas de la CHR ROM. Le mappage et le mirroring PPU s’appliquent. Pour les copies et remplissages contigus, choisissez l’incrément 1 dans PPUCTRL. Exécutez à un instant sûr, puis rétablissez le défilement voulu ; l’exécuteur écrit PPUADDR et PPUDATA sans restaurer le défilement.

src

Pointeur CPU vers au moins le nombre demandé d’octets lisibles. Maintenez la source accessible et stable jusqu’au retour de ce transfert synchrone. Aucun changement de banque, stockage du pointeur dans une file ou contrôle de longueur n’est effectué ; un nombre nul ne déréférence pas le pointeur.

len

Nombre d’octets de 0..255. Zéro définit aussi l’adresse PPU et réinitialise le verrou, sans écrire de données. Aucun contrôle de capacité, découpage, changement de banque ou transfert différé n’est prévu.

Valeur renvoyée

Aucune (void).

Conditions et limites

L’accès direct à la PPU exige de désactiver le rendu ou de terminer toute l’opération dans un intervalle VBlank sûr. Ces fonctions n’attendent pas, ne mettent rien en file, ne bloquent pas NMI et ne rétablissent pas le défilement. Empêchez une interruption de modifier le verrou ou l’adresse partagés pendant l’appel. Le bit 2 de PPUCTRL choisit un pas de destination de 1 ou 32 par octet. La PPU gère les miroirs et le bouclage des adresses ; la CHR-ROM n’est pas inscriptible. Rétablissez le défilement voulu avant le rendu.

NESdev : registres du PPU

NESdev : défilement du PPU

Exemple d’utilisation

c
__vram_write(0x2040,pattern,32); // Blue row: solid tile, left half, triangle.
pattern[0]=3; // The completed transfer keeps its first solid tile.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente les registres, les écritures directes de tuiles et d’attributs, le défilement et le chargement séparé des palettes de fond et de sprites. La bande bleue en (0,16) mesure 256×16 pixels : la première ligne répète une tuile pleine de 8×8, une barre gauche de 4×8 et un triangle de huit lignes ; la seconde contient uniquement des barres. Un rectangle vert plein de 64×32 commence en (16,48). La région rouge de 64×32 en (144,48) contient une grille de 8 par 4 tuiles triangulaires.

À y=96, les repères bleus en x=16, 24 et 32 sont un carré, une barre gauche et un triangle ; une barre gauche rouge est en x=80 et un triangle vert en x=144. Ce sont des tuiles de fond. À y=144, les sprites avec leur propre palette sont un carré rouge de 8×8 en x=16, une barre gauche bleue de 4×8 en x=80 et un triangle vert de huit lignes en x=144. Le défilement final vaut (0,0).

L’image vérifie formes et couleurs sur 16 384 pixels, sprites compris. Les 31 tests d’état couvrent registres, effets d’état/verrou, défilement mémorisé, transferts de 0/255 octets, incréments 1/32, bords, dimensions nulles, tables, attributs complets et les deux palettes. Y=255 est vérifié comme écriture de registre, pas comme image de défilement correcte. Les résultats proviennent de KUROSAKI avec NROM ; le matériel réel n’a pas été testé.

__vram_write: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:120

Notes de déclaration (texte source)
Write a byte-counted source range directly; arrange PPU access timing and source-bank visibility.

kitaqfc/kitaqfc/CodeGenerator.cs:4817

csharp
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

Renvoie la capacité totale du tampon d’ordres, quelle que soit son occupation actuelle.

Syntaxe

c
u8 __vramq_capacity(void);

Valeur renvoyée

Un octet non signé dans les unités propres à la plateforme décrites ici. Sa lecture ne modifie ni n’exécute la file.

Conditions et limites

FC compte les octets d’ordres encodés dans un tampon intrinsèque fixe de 128 octets. Une écriture de tuile coûte 4 octets, un remplissage 5 et une copie par pointeur 6, métadonnées comprises. NMI peut consommer une file prête ; inspectez-la et construisez-la avant validation, en coordonnant production et consommation.

Ces intrinsèques utilisent le même tampon que lib/vram.c. Les fonctions nes_vram_queue_* de runtime.c gèrent une autre file, au format différent. Vider ou interroger l’une ne vide ni ne mesure l’autre.

Coordonnez la construction et la consommation de la file. N’ajoutez rien pendant son exécution par une interruption, ne modifiez pas directement ses compteurs et n’écrasez pas involontairement les sources conservées. Ces API n’offrent ni verrouillage ni double tampon. La capacité concerne le tampon d’ordres, pas les octets libres de VRAM physique.

Exemple d’utilisation

c
capacity=__vramq_capacity();
if (capacity!=128) failures++;

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente capacité, pointeurs conservés, remises à zéro indépendantes et exécution directe ou par NMI. On attend CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 et FAILED CHECKS 000. Six enregistrements occupent 30 octets et effectuent 512 écritures d’un octet. OVERFLOW SEEN mémorise le rejet volontaire précédent ; il ne décrit pas un indicateur final encore actif.

La copie bleue commence au pixel (0,16), le remplissage vert en (0,88). Chacun couvre sept lignes complètes de 256 pixels, puis 248 pixels de la huitième : 255 tuiles au total. La dernière case de 8 × 8 reste noire. La copie répète des motifs pleins, à moitié gauche et triangulaires ; le premier est triangulaire car la source change après l’ajout. Le remplissage reste plein malgré le changement de variable chez l’appelant. Forme et couleur sont vérifiées.

À y=160, on attend un carré rouge plein à x=16, une demi-tuile gauche rouge à x=224 et un carré rouge plein à x=240. Le premier subsiste après les enregistrements de longueur nulle ; le milieu prouve le traitement par NMI ; le dernier, un put. La case annulée en (248,160) reste noire. Les longs transferts s’effectuent avec rendu et NMI désactivés ; seule la petite écriture finale utilise NMI. Il s’agit d’un test sur émulateur NROM, pas sur matériel réel.

__vramq_capacity: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:164

Notes de déclaration (texte source)
Return total encoded command-buffer capacity; this is independent of PPU VRAM capacity.
__vramq_clear

Abandonne tous les enregistrements en attente et remet longueur et drapeau prêt à zéro. N’écrit pas en VRAM et n’efface pas le débordement. Appelez séparément __vramq_clear_overflow pour effacer cet indicateur.

Syntaxe

c
void __vramq_clear(void);

Valeur renvoyée

Aucune (void).

Conditions et limites

FC compte les octets d’ordres encodés dans un tampon intrinsèque fixe de 128 octets. Une écriture de tuile coûte 4 octets, un remplissage 5 et une copie par pointeur 6, métadonnées comprises. NMI peut consommer une file prête ; inspectez-la et construisez-la avant validation, en coordonnant production et consommation.

Ces intrinsèques utilisent le même tampon que lib/vram.c. Les fonctions nes_vram_queue_* de runtime.c gèrent une autre file, au format différent. Vider ou interroger l’une ne vide ni ne mesure l’autre.

Coordonnez la construction et la consommation de la file. N’ajoutez rien pendant son exécution par une interruption, ne modifiez pas directement ses compteurs et n’écrasez pas involontairement les sources conservées. Ces API n’offrent ni verrouillage ni double tampon. La capacité concerne le tampon d’ordres, pas les octets libres de VRAM physique.

Exemple d’utilisation

c
__vramq_clear();
if (__vramq_len()!=0 || __vramq_overflow()!=1) failures++;
// Clearing queued work deliberately leaves the overflow latch set.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente capacité, pointeurs conservés, remises à zéro indépendantes et exécution directe ou par NMI. On attend CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 et FAILED CHECKS 000. Six enregistrements occupent 30 octets et effectuent 512 écritures d’un octet. OVERFLOW SEEN mémorise le rejet volontaire précédent ; il ne décrit pas un indicateur final encore actif.

La copie bleue commence au pixel (0,16), le remplissage vert en (0,88). Chacun couvre sept lignes complètes de 256 pixels, puis 248 pixels de la huitième : 255 tuiles au total. La dernière case de 8 × 8 reste noire. La copie répète des motifs pleins, à moitié gauche et triangulaires ; le premier est triangulaire car la source change après l’ajout. Le remplissage reste plein malgré le changement de variable chez l’appelant. Forme et couleur sont vérifiées.

À y=160, on attend un carré rouge plein à x=16, une demi-tuile gauche rouge à x=224 et un carré rouge plein à x=240. Le premier subsiste après les enregistrements de longueur nulle ; le milieu prouve le traitement par NMI ; le dernier, un put. La case annulée en (248,160) reste noire. Les longs transferts s’effectuent avec rendu et NMI désactivés ; seule la petite écriture finale utilise NMI. Il s’agit d’un test sur émulateur NROM, pas sur matériel réel.

__vramq_clear: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:146

Notes de déclaration (texte source)
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

Remet uniquement l’indicateur de débordement à zéro. Les enregistrements, la longueur et l’état prêt restent inchangés. Acquitte le manque de place sans réessayer l’enregistrement rejeté ni annuler les ajouts précédents réussis.

Syntaxe

c
void __vramq_clear_overflow(void);

Valeur renvoyée

Aucune (void).

Conditions et limites

FC compte les octets d’ordres encodés dans un tampon intrinsèque fixe de 128 octets. Une écriture de tuile coûte 4 octets, un remplissage 5 et une copie par pointeur 6, métadonnées comprises. NMI peut consommer une file prête ; inspectez-la et construisez-la avant validation, en coordonnant production et consommation.

Ces intrinsèques utilisent le même tampon que lib/vram.c. Les fonctions nes_vram_queue_* de runtime.c gèrent une autre file, au format différent. Vider ou interroger l’une ne vide ni ne mesure l’autre.

Coordonnez la construction et la consommation de la file. N’ajoutez rien pendant son exécution par une interruption, ne modifiez pas directement ses compteurs et n’écrasez pas involontairement les sources conservées. Ces API n’offrent ni verrouillage ni double tampon. La capacité concerne le tampon d’ordres, pas les octets libres de VRAM physique.

Exemple d’utilisation

c
__vramq_clear_overflow();
if (__vramq_overflow()!=0 || __vramq_len()!=4) failures++;
// Resetting the latch deliberately keeps this pending put record.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente capacité, pointeurs conservés, remises à zéro indépendantes et exécution directe ou par NMI. On attend CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 et FAILED CHECKS 000. Six enregistrements occupent 30 octets et effectuent 512 écritures d’un octet. OVERFLOW SEEN mémorise le rejet volontaire précédent ; il ne décrit pas un indicateur final encore actif.

La copie bleue commence au pixel (0,16), le remplissage vert en (0,88). Chacun couvre sept lignes complètes de 256 pixels, puis 248 pixels de la huitième : 255 tuiles au total. La dernière case de 8 × 8 reste noire. La copie répète des motifs pleins, à moitié gauche et triangulaires ; le premier est triangulaire car la source change après l’ajout. Le remplissage reste plein malgré le changement de variable chez l’appelant. Forme et couleur sont vérifiées.

À y=160, on attend un carré rouge plein à x=16, une demi-tuile gauche rouge à x=224 et un carré rouge plein à x=240. Le premier subsiste après les enregistrements de longueur nulle ; le milieu prouve le traitement par NMI ; le dernier, un put. La case annulée en (248,160) reste noire. Les longs transferts s’effectuent avec rendu et NMI désactivés ; seule la petite écriture finale utilise NMI. Il s’agit d’un test sur émulateur NROM, pas sur matériel réel.

__vramq_clear_overflow: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:162

Notes de déclaration (texte source)
Clear the overflow latch explicitly after handling the failed submission.
__vramq_commit

Active le drapeau prêt afin que l’exécuteur puisse consommer la file. Ne copie pas les données, n’écrit pas dans la PPU et n’attend pas NMI. Après validation, gardez la file et les sources conservées stables jusqu’à la fin. Si NMI est active, le gestionnaire par défaut peut traiter la file de façon asynchrone.

Syntaxe

c
void __vramq_commit(void);

Valeur renvoyée

Aucune (void).

Conditions et limites

FC compte les octets d’ordres encodés dans un tampon intrinsèque fixe de 128 octets. Une écriture de tuile coûte 4 octets, un remplissage 5 et une copie par pointeur 6, métadonnées comprises. NMI peut consommer une file prête ; inspectez-la et construisez-la avant validation, en coordonnant production et consommation.

Ces intrinsèques utilisent le même tampon que lib/vram.c. Les fonctions nes_vram_queue_* de runtime.c gèrent une autre file, au format différent. Vider ou interroger l’une ne vide ni ne mesure l’autre.

Coordonnez la construction et la consommation de la file. N’ajoutez rien pendant son exécution par une interruption, ne modifiez pas directement ses compteurs et n’écrasez pas involontairement les sources conservées. Ces API n’offrent ni verrouillage ni double tampon. La capacité concerne le tampon d’ordres, pas les octets libres de VRAM physique.

Exemple d’utilisation

c
__vramq_commit(); // NMI is disabled here: this only marks the queue ready.
if (__vramq_len()!=30) failures++;

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente capacité, pointeurs conservés, remises à zéro indépendantes et exécution directe ou par NMI. On attend CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 et FAILED CHECKS 000. Six enregistrements occupent 30 octets et effectuent 512 écritures d’un octet. OVERFLOW SEEN mémorise le rejet volontaire précédent ; il ne décrit pas un indicateur final encore actif.

La copie bleue commence au pixel (0,16), le remplissage vert en (0,88). Chacun couvre sept lignes complètes de 256 pixels, puis 248 pixels de la huitième : 255 tuiles au total. La dernière case de 8 × 8 reste noire. La copie répète des motifs pleins, à moitié gauche et triangulaires ; le premier est triangulaire car la source change après l’ajout. Le remplissage reste plein malgré le changement de variable chez l’appelant. Forme et couleur sont vérifiées.

À y=160, on attend un carré rouge plein à x=16, une demi-tuile gauche rouge à x=224 et un carré rouge plein à x=240. Le premier subsiste après les enregistrements de longueur nulle ; le milieu prouve le traitement par NMI ; le dernier, un put. La case annulée en (248,160) reste noire. Les longs transferts s’effectuent avec rendu et NMI désactivés ; seule la petite écriture finale utilise NMI. Il s’agit d’un test sur émulateur NROM, pas sur matériel réel.

__vramq_commit: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:154

Notes de déclaration (texte source)
Publish the pending records to the configured consumer. Do not mutate them while consumption can run.
__vramq_copy

Ajoute un enregistrement de six octets pour copier len octets de src vers ppu_addr à l’exécution. Conserve le pointeur, pas les données. Même une copie de 255 octets n’occupe que six octets de file. Lit la source par adresses croissantes ; choisissez l’incrément PPU 1 pour une destination contiguë.

Syntaxe

c
void __vramq_copy(u16 ppu_addr, const u8* src, u8 len);

Paramètres

ppu_addr

Adresse PPU en octets (0x0000–0x3FFF), et non pointeur CPU. La destination doit être inscriptible : modifier les motifs exige de la CHR RAM, pas de la CHR ROM. Le mappage et le mirroring PPU s’appliquent. Pour les copies et remplissages contigus, choisissez l’incrément 1 dans PPUCTRL. Exécutez à un instant sûr, puis rétablissez le défilement voulu ; l’exécuteur écrit PPUADDR et PPUDATA sans restaurer le défilement.

src

Pointeur proche vers des octets lisibles, conservé jusqu’à l’exécution. Gardez les données vivantes et leur banque mémoire ou ROM accessible pendant tout le traitement ; aucun numéro de banque ni copie n’est mémorisé. Un transfert non vide exige une source valide. N’utilisez pas un tableau local dont la durée de vie est terminée.

len

Nombre d’octets, de 0 à 255. Un seul enregistrement traite tout le compte, sans découpage en morceaux de 127 octets. Zéro occupe encore un enregistrement de copie ou de remplissage. À l’exécution, l’adresse PPU est réglée mais aucun octet n’est écrit dans PPUDATA. 256 est hors plage.

Valeur renvoyée

Aucune (void).

Conditions et limites

L’ajout ne renvoie pas de valeur de réussite. Si l’enregistrement entier ne tient pas, active __vramq_overflow() sans modifier les enregistrements ni la longueur. Une place exactement suffisante est acceptée. Un ancien indicateur n’empêche pas un ajout qui tient ; traitez-le ou effacez-le avant d’évaluer un nouveau lot. Plusieurs ajouts ne forment pas une transaction.

FC compte les octets d’ordres encodés dans un tampon intrinsèque fixe de 128 octets. Une écriture de tuile coûte 4 octets, un remplissage 5 et une copie par pointeur 6, métadonnées comprises. NMI peut consommer une file prête ; inspectez-la et construisez-la avant validation, en coordonnant production et consommation.

Ces intrinsèques utilisent le même tampon que lib/vram.c. Les fonctions nes_vram_queue_* de runtime.c gèrent une autre file, au format différent. Vider ou interroger l’une ne vide ni ne mesure l’autre.

Coordonnez la construction et la consommation de la file. N’ajoutez rien pendant son exécution par une interruption, ne modifiez pas directement ses compteurs et n’écrasez pas involontairement les sources conservées. Ces API n’offrent ni verrouillage ni double tampon. La capacité concerne le tampon d’ordres, pas les octets libres de VRAM physique.

Exemple d’utilisation

c
__vramq_copy(0x2040,source_bytes,255);
source_bytes[0]=3; // The executor must see this change through its retained pointer.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente capacité, pointeurs conservés, remises à zéro indépendantes et exécution directe ou par NMI. On attend CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 et FAILED CHECKS 000. Six enregistrements occupent 30 octets et effectuent 512 écritures d’un octet. OVERFLOW SEEN mémorise le rejet volontaire précédent ; il ne décrit pas un indicateur final encore actif.

La copie bleue commence au pixel (0,16), le remplissage vert en (0,88). Chacun couvre sept lignes complètes de 256 pixels, puis 248 pixels de la huitième : 255 tuiles au total. La dernière case de 8 × 8 reste noire. La copie répète des motifs pleins, à moitié gauche et triangulaires ; le premier est triangulaire car la source change après l’ajout. Le remplissage reste plein malgré le changement de variable chez l’appelant. Forme et couleur sont vérifiées.

À y=160, on attend un carré rouge plein à x=16, une demi-tuile gauche rouge à x=224 et un carré rouge plein à x=240. Le premier subsiste après les enregistrements de longueur nulle ; le milieu prouve le traitement par NMI ; le dernier, un put. La case annulée en (248,160) reste noire. Les longs transferts s’effectuent avec rendu et NMI désactivés ; seule la petite écriture finale utilise NMI. Il s’agit d’un test sur émulateur NROM, pas sur matériel réel.

__vramq_copy: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:150

Notes de déclaration (texte source)
Retain the source address for later consumption; keep its storage and bank mapping valid.
__vramq_exec

Exécute immédiatement une file validée, dans l’ordre des enregistrements. Si elle n’est pas prête, revient sans la modifier. À la fin, efface longueur et état prêt, mais conserve le débordement. N’attend pas un instant sûr pour la PPU, ne limite pas le transfert et ne restaure pas le défilement : ces conditions relèvent de l’appelant.

Syntaxe

c
void __vramq_exec(void);

Valeur renvoyée

Aucune (void).

Conditions et limites

Adresse PPU en octets (0x0000–0x3FFF), et non pointeur CPU. La destination doit être inscriptible : modifier les motifs exige de la CHR RAM, pas de la CHR ROM. Le mappage et le mirroring PPU s’appliquent. Pour les copies et remplissages contigus, choisissez l’incrément 1 dans PPUCTRL. Exécutez à un instant sûr, puis rétablissez le défilement voulu ; l’exécuteur écrit PPUADDR et PPUDATA sans restaurer le défilement.

FC compte les octets d’ordres encodés dans un tampon intrinsèque fixe de 128 octets. Une écriture de tuile coûte 4 octets, un remplissage 5 et une copie par pointeur 6, métadonnées comprises. NMI peut consommer une file prête ; inspectez-la et construisez-la avant validation, en coordonnant production et consommation.

Ces intrinsèques utilisent le même tampon que lib/vram.c. Les fonctions nes_vram_queue_* de runtime.c gèrent une autre file, au format différent. Vider ou interroger l’une ne vide ni ne mesure l’autre.

Coordonnez la construction et la consommation de la file. N’ajoutez rien pendant son exécution par une interruption, ne modifiez pas directement ses compteurs et n’écrasez pas involontairement les sources conservées. Ces API n’offrent ni verrouillage ni double tampon. La capacité concerne le tampon d’ordres, pas les octets libres de VRAM physique.

Exemple d’utilisation

c
// Rendering is stopped and the PPU increment is 1.
__vramq_exec();
after_exec=__vramq_len();
if (after_exec!=0 || __vramq_overflow()!=0) failures++;

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente capacité, pointeurs conservés, remises à zéro indépendantes et exécution directe ou par NMI. On attend CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 et FAILED CHECKS 000. Six enregistrements occupent 30 octets et effectuent 512 écritures d’un octet. OVERFLOW SEEN mémorise le rejet volontaire précédent ; il ne décrit pas un indicateur final encore actif.

La copie bleue commence au pixel (0,16), le remplissage vert en (0,88). Chacun couvre sept lignes complètes de 256 pixels, puis 248 pixels de la huitième : 255 tuiles au total. La dernière case de 8 × 8 reste noire. La copie répète des motifs pleins, à moitié gauche et triangulaires ; le premier est triangulaire car la source change après l’ajout. Le remplissage reste plein malgré le changement de variable chez l’appelant. Forme et couleur sont vérifiées.

À y=160, on attend un carré rouge plein à x=16, une demi-tuile gauche rouge à x=224 et un carré rouge plein à x=240. Le premier subsiste après les enregistrements de longueur nulle ; le milieu prouve le traitement par NMI ; le dernier, un put. La case annulée en (248,160) reste noire. Les longs transferts s’effectuent avec rendu et NMI désactivés ; seule la petite écriture finale utilise NMI. Il s’agit d’un test sur émulateur NROM, pas sur matériel réel.

__vramq_exec: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:156

Notes de déclaration (texte source)
Execute a ready queue directly; committing and safe PPU timing are caller responsibilities.
__vramq_fill

Ajoute un enregistrement de cinq octets répétant l’octet value pendant len écritures PPU. La valeur est stockée : modifier ensuite la variable de l’appelant ne change pas le remplissage en attente. Répète un octet, pas un motif graphique de plusieurs octets.

Syntaxe

c
void __vramq_fill(u16 ppu_addr, u8 value, u8 len);

Paramètres

ppu_addr

Adresse PPU en octets (0x0000–0x3FFF), et non pointeur CPU. La destination doit être inscriptible : modifier les motifs exige de la CHR RAM, pas de la CHR ROM. Le mappage et le mirroring PPU s’appliquent. Pour les copies et remplissages contigus, choisissez l’incrément 1 dans PPUCTRL. Exécutez à un instant sûr, puis rétablissez le défilement voulu ; l’exécuteur écrit PPUADDR et PPUDATA sans restaurer le défilement.

value

Un octet de 0 à 255 répété. Son effet visible dépend du contenu de destination : indices de tuiles, plans de pixels ou autre structure d’octets.

len

Nombre d’octets, de 0 à 255. Un seul enregistrement traite tout le compte, sans découpage en morceaux de 127 octets. Zéro occupe encore un enregistrement de copie ou de remplissage. À l’exécution, l’adresse PPU est réglée mais aucun octet n’est écrit dans PPUDATA. 256 est hors plage.

Valeur renvoyée

Aucune (void).

Conditions et limites

L’ajout ne renvoie pas de valeur de réussite. Si l’enregistrement entier ne tient pas, active __vramq_overflow() sans modifier les enregistrements ni la longueur. Une place exactement suffisante est acceptée. Un ancien indicateur n’empêche pas un ajout qui tient ; traitez-le ou effacez-le avant d’évaluer un nouveau lot. Plusieurs ajouts ne forment pas une transaction.

FC compte les octets d’ordres encodés dans un tampon intrinsèque fixe de 128 octets. Une écriture de tuile coûte 4 octets, un remplissage 5 et une copie par pointeur 6, métadonnées comprises. NMI peut consommer une file prête ; inspectez-la et construisez-la avant validation, en coordonnant production et consommation.

Ces intrinsèques utilisent le même tampon que lib/vram.c. Les fonctions nes_vram_queue_* de runtime.c gèrent une autre file, au format différent. Vider ou interroger l’une ne vide ni ne mesure l’autre.

Coordonnez la construction et la consommation de la file. N’ajoutez rien pendant son exécution par une interruption, ne modifiez pas directement ses compteurs et n’écrasez pas involontairement les sources conservées. Ces API n’offrent ni verrouillage ni double tampon. La capacité concerne le tampon d’ordres, pas les octets libres de VRAM physique.

Exemple d’utilisation

c
__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.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente capacité, pointeurs conservés, remises à zéro indépendantes et exécution directe ou par NMI. On attend CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 et FAILED CHECKS 000. Six enregistrements occupent 30 octets et effectuent 512 écritures d’un octet. OVERFLOW SEEN mémorise le rejet volontaire précédent ; il ne décrit pas un indicateur final encore actif.

La copie bleue commence au pixel (0,16), le remplissage vert en (0,88). Chacun couvre sept lignes complètes de 256 pixels, puis 248 pixels de la huitième : 255 tuiles au total. La dernière case de 8 × 8 reste noire. La copie répète des motifs pleins, à moitié gauche et triangulaires ; le premier est triangulaire car la source change après l’ajout. Le remplissage reste plein malgré le changement de variable chez l’appelant. Forme et couleur sont vérifiées.

À y=160, on attend un carré rouge plein à x=16, une demi-tuile gauche rouge à x=224 et un carré rouge plein à x=240. Le premier subsiste après les enregistrements de longueur nulle ; le milieu prouve le traitement par NMI ; le dernier, un put. La case annulée en (248,160) reste noire. Les longs transferts s’effectuent avec rendu et NMI désactivés ; seule la petite écriture finale utilise NMI. Il s’agit d’un test sur émulateur NROM, pas sur matériel réel.

__vramq_fill: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:152

Notes de déclaration (texte source)
Append a repeated-byte update; the source value is stored in the record.
__vramq_len

Renvoie l’espace du tampon occupé par les ordres en attente, pas le nombre d’octets qui seront écrits en mémoire vidéo.

Syntaxe

c
u8 __vramq_len(void);

Valeur renvoyée

Un octet non signé dans les unités propres à la plateforme décrites ici. Sa lecture ne modifie ni n’exécute la file.

Conditions et limites

FC compte les octets d’ordres encodés dans un tampon intrinsèque fixe de 128 octets. Une écriture de tuile coûte 4 octets, un remplissage 5 et une copie par pointeur 6, métadonnées comprises. NMI peut consommer une file prête ; inspectez-la et construisez-la avant validation, en coordonnant production et consommation.

Ces intrinsèques utilisent le même tampon que lib/vram.c. Les fonctions nes_vram_queue_* de runtime.c gèrent une autre file, au format différent. Vider ou interroger l’une ne vide ni ne mesure l’autre.

Coordonnez la construction et la consommation de la file. N’ajoutez rien pendant son exécution par une interruption, ne modifiez pas directement ses compteurs et n’écrasez pas involontairement les sources conservées. Ces API n’offrent ni verrouillage ni double tampon. La capacité concerne le tampon d’ordres, pas les octets libres de VRAM physique.

Exemple d’utilisation

c
encoded=__vramq_len();
if (encoded!=30 || capacity-encoded!=98) failures++;
// 4+6+5+6+5+4 encoded bytes describe 512 actual byte writes.

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente capacité, pointeurs conservés, remises à zéro indépendantes et exécution directe ou par NMI. On attend CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 et FAILED CHECKS 000. Six enregistrements occupent 30 octets et effectuent 512 écritures d’un octet. OVERFLOW SEEN mémorise le rejet volontaire précédent ; il ne décrit pas un indicateur final encore actif.

La copie bleue commence au pixel (0,16), le remplissage vert en (0,88). Chacun couvre sept lignes complètes de 256 pixels, puis 248 pixels de la huitième : 255 tuiles au total. La dernière case de 8 × 8 reste noire. La copie répète des motifs pleins, à moitié gauche et triangulaires ; le premier est triangulaire car la source change après l’ajout. Le remplissage reste plein malgré le changement de variable chez l’appelant. Forme et couleur sont vérifiées.

À y=160, on attend un carré rouge plein à x=16, une demi-tuile gauche rouge à x=224 et un carré rouge plein à x=240. Le premier subsiste après les enregistrements de longueur nulle ; le milieu prouve le traitement par NMI ; le dernier, un put. La case annulée en (248,160) reste noire. Les longs transferts s’effectuent avec rendu et NMI désactivés ; seule la petite écriture finale utilise NMI. Il s’agit d’un test sur émulateur NROM, pas sur matériel réel.

__vramq_len: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:158

Notes de déclaration (texte source)
Return encoded bytes currently occupied, not transfer payload bytes or command count.
__vramq_overflow

Lit le drapeau de débordement sans l’effacer ; il peut conserver un échec antérieur.

Syntaxe

c
u8 __vramq_overflow(void);

Valeur renvoyée

0 si aucun débordement n’est mémorisé ; 1 après un ajout rejeté, jusqu’à une remise à zéro explicite. L’exécution de la file ne l’efface pas.

Conditions et limites

FC compte les octets d’ordres encodés dans un tampon intrinsèque fixe de 128 octets. Une écriture de tuile coûte 4 octets, un remplissage 5 et une copie par pointeur 6, métadonnées comprises. NMI peut consommer une file prête ; inspectez-la et construisez-la avant validation, en coordonnant production et consommation.

Ces intrinsèques utilisent le même tampon que lib/vram.c. Les fonctions nes_vram_queue_* de runtime.c gèrent une autre file, au format différent. Vider ou interroger l’une ne vide ni ne mesure l’autre.

Coordonnez la construction et la consommation de la file. N’ajoutez rien pendant son exécution par une interruption, ne modifiez pas directement ses compteurs et n’écrasez pas involontairement les sources conservées. Ces API n’offrent ni verrouillage ni double tampon. La capacité concerne le tampon d’ordres, pas les octets libres de VRAM physique.

Exemple d’utilisation

c
overflow_seen=__vramq_overflow();
if (overflow_seen!=1 || __vramq_overflow()!=1) failures++;

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente capacité, pointeurs conservés, remises à zéro indépendantes et exécution directe ou par NMI. On attend CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 et FAILED CHECKS 000. Six enregistrements occupent 30 octets et effectuent 512 écritures d’un octet. OVERFLOW SEEN mémorise le rejet volontaire précédent ; il ne décrit pas un indicateur final encore actif.

La copie bleue commence au pixel (0,16), le remplissage vert en (0,88). Chacun couvre sept lignes complètes de 256 pixels, puis 248 pixels de la huitième : 255 tuiles au total. La dernière case de 8 × 8 reste noire. La copie répète des motifs pleins, à moitié gauche et triangulaires ; le premier est triangulaire car la source change après l’ajout. Le remplissage reste plein malgré le changement de variable chez l’appelant. Forme et couleur sont vérifiées.

À y=160, on attend un carré rouge plein à x=16, une demi-tuile gauche rouge à x=224 et un carré rouge plein à x=240. Le premier subsiste après les enregistrements de longueur nulle ; le milieu prouve le traitement par NMI ; le dernier, un put. La case annulée en (248,160) reste noire. Les longs transferts s’effectuent avec rendu et NMI désactivés ; seule la petite écriture finale utilise NMI. Il s’agit d’un test sur émulateur NROM, pas sur matériel réel.

__vramq_overflow: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:160

Notes de déclaration (texte source)
Read the latched capacity failure; reading does not reset it.
__vramq_put

Ajoute une adresse PPU et une valeur d’un octet dans un enregistrement de quatre octets. La valeur est copiée dans la file maintenant et écrite à l’exécution. Permet notamment de modifier une tuile de table de noms, ou un autre octet PPU inscriptible.

Syntaxe

c
void __vramq_put(u16 ppu_addr, u8 value);

Paramètres

ppu_addr

Adresse PPU en octets (0x0000–0x3FFF), et non pointeur CPU. La destination doit être inscriptible : modifier les motifs exige de la CHR RAM, pas de la CHR ROM. Le mappage et le mirroring PPU s’appliquent. Pour les copies et remplissages contigus, choisissez l’incrément 1 dans PPUCTRL. Exécutez à un instant sûr, puis rétablissez le défilement voulu ; l’exécuteur écrit PPUADDR et PPUDATA sans restaurer le défilement.

value

Un octet de 0 à 255 répété. Son effet visible dépend du contenu de destination : indices de tuiles, plans de pixels ou autre structure d’octets.

Valeur renvoyée

Aucune (void).

Conditions et limites

L’ajout ne renvoie pas de valeur de réussite. Si l’enregistrement entier ne tient pas, active __vramq_overflow() sans modifier les enregistrements ni la longueur. Une place exactement suffisante est acceptée. Un ancien indicateur n’empêche pas un ajout qui tient ; traitez-le ou effacez-le avant d’évaluer un nouveau lot. Plusieurs ajouts ne forment pas une transaction.

FC compte les octets d’ordres encodés dans un tampon intrinsèque fixe de 128 octets. Une écriture de tuile coûte 4 octets, un remplissage 5 et une copie par pointeur 6, métadonnées comprises. NMI peut consommer une file prête ; inspectez-la et construisez-la avant validation, en coordonnant production et consommation.

Ces intrinsèques utilisent le même tampon que lib/vram.c. Les fonctions nes_vram_queue_* de runtime.c gèrent une autre file, au format différent. Vider ou interroger l’une ne vide ni ne mesure l’autre.

Coordonnez la construction et la consommation de la file. N’ajoutez rien pendant son exécution par une interruption, ne modifiez pas directement ses compteurs et n’écrasez pas involontairement les sources conservées. Ces API n’offrent ni verrouillage ni double tampon. La capacité concerne le tampon d’ordres, pas les octets libres de VRAM physique.

Exemple d’utilisation

c
__vramq_put(0x2282,1); // Red 8x8 sentinel at pixel (16,160).

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Le programme présente capacité, pointeurs conservés, remises à zéro indépendantes et exécution directe ou par NMI. On attend CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 et FAILED CHECKS 000. Six enregistrements occupent 30 octets et effectuent 512 écritures d’un octet. OVERFLOW SEEN mémorise le rejet volontaire précédent ; il ne décrit pas un indicateur final encore actif.

La copie bleue commence au pixel (0,16), le remplissage vert en (0,88). Chacun couvre sept lignes complètes de 256 pixels, puis 248 pixels de la huitième : 255 tuiles au total. La dernière case de 8 × 8 reste noire. La copie répète des motifs pleins, à moitié gauche et triangulaires ; le premier est triangulaire car la source change après l’ajout. Le remplissage reste plein malgré le changement de variable chez l’appelant. Forme et couleur sont vérifiées.

À y=160, on attend un carré rouge plein à x=16, une demi-tuile gauche rouge à x=224 et un carré rouge plein à x=240. Le premier subsiste après les enregistrements de longueur nulle ; le milieu prouve le traitement par NMI ; le dernier, un put. La case annulée en (248,160) reste noire. Les longs transferts s’effectuent avec rendu et NMI désactivés ; seule la petite écriture finale utilise NMI. Il s’agit d’un test sur émulateur NROM, pas sur matériel réel.

__vramq_put: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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-disasm
Implémentation et source

kitaqfc/lib/intrinsics.h:148

Notes de déclaration (texte source)
Append one address/value update; use the overflow latch to detect capacity failure.
__xy_in_rect

Teste l’appartenance à un rectangle, bords supérieur/gauche inclus et inférieur/droit exclus.

Syntaxe

c
u8 __xy_in_rect(u8 x, u8 y, u8 rx, u8 ry, u8 rw, u8 rh);

Paramètres

x

X du point, de 0 à 255.

y

Y du point, de 0 à 255.

rx

Bord gauche du rectangle, de 0 à 255.

ry

Bord supérieur du rectangle, de 0 à 255.

rw

Largeur de 0 à 255.

rh

Hauteur de 0 à 255.

Valeur renvoyée

Renvoie 1 à l’intérieur, sinon 0. Une largeur ou une hauteur nulle donne un rectangle vide.

Conditions et limites

Utilise les différences de coordonnées : un bord droit supérieur à 255 ne reboucle pas vers la gauche.

Exemple d’utilisation

c
result[0]=__xy_in_rect(10,20,10,20,3,2);result[1]=__xy_in_rect(13,20,10,20,3,2);result[2]=__xy_in_rect(9,20,10,20,3,2);result[3]=__xy_in_rect(255,1,250,0,10,2);result[4]=__xy_in_rect(10,20,10,20,0,2);

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Testez intérieur, limites, point extérieur et largeur nulle : INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1. Vérifiez la distance 15, la distance rebouclée 254 et les indices de tableau 64/65280.

__xy_in_rect: NROM
NROM

Programme d’exemple complet

Commande de compilation

powershell
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.chr
Implémentation et source

kitaqfc/lib/intrinsics.h:31

Notes de déclaration (texte source)
Return 0 or 1 for half-open bounds. Zero extents are empty; coordinates do not wrap across 255.

kitaqfc/kitaqfc/CodeGenerator.cs:4627

csharp
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

Fonction de lecture de lumière par défaut, sur le port 2. Appelle la même routine que __zapper_light2().

Syntaxe

c
u8 __zapper_light(void);

Valeur renvoyée

1 si le bit 3 de $4017 est à 0, sinon 0. Le port 1 n’est pas lu.

Conditions et limites

Ces API accèdent directement à $4016/$4017. N’entrelacez pas sur les mêmes ports des opérations de manette, clavier ou série pilotées par interruption. Les positions de décalage des contrôleurs peuvent aussi être affectées. L’exemple désactive la NMI pendant la capture des entrées, puis l’active à l’initialisation de l’affichage.

Lit le signal instantané. Ne génère pas de strobe, n’attend pas une image, ne détecte pas de front, ne localise pas la visée et ne décide pas d’un impact. Les appels bruts et ceux de gâchette/lumière lisent chacun le registre à leur propre instant ; leurs valeurs peuvent donc différer si le signal change.

L’entrée lumineuse est active à l’état bas : le bit 3 à zéro produit un résultat de un. Cette conversion ne détecte pas la connexion d’un pistolet ; une manette ou une entrée débranchée peut aussi donner un. Dessiner une cible à l’écran n’en fait pas une API de détection d’impact.

La conversion des bits est aussi vérifiée dans une ROM de test créée pour ce projet, en redirigeant les lectures de registres vers des valeurs de test en RAM. Les cas couvrent les combinaisons 00/08/10/18 avec d’autres bits à 1. Ce test unitaire est distinct de l’exécution de l’exemple non modifié et du comportement d’un périphérique réel.

Ces images exécutent les ROM téléchargeables sans modification dans KUROSAKI. Son bus d’entrée modélise les manettes, mais pas les signaux du clavier, du pistolet optique ou d’un adaptateur série. Les bits d’entrée externes se lisent à zéro ; les captures ne valident donc pas ces appareils. Les touches physiques, la détection optique et la communication restent non testées.

NESdev: Zapper

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.

__zapper_light: ZAPPER-DEFAULT — Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.

Programme d’exemple complet

Commande de compilation

powershell
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.png
Implémentation et source

kitaqfc/lib/intrinsics.h:253

Notes de déclaration (texte source)
Default to the port-2 normalized light reader.

kitaqfc/kitaqfc/CodeGenerator.cs:7753

csharp
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

Lit le signal lumineux du port 1 ($4016) et convertit cette entrée active à l’état bas pour renvoyer 1 lorsque de la lumière est détectée.

Syntaxe

c
u8 __zapper_light1(void);

Valeur renvoyée

1 si le bit 3 de $4016 est à 0 ; 0 s’il est à 1.

Conditions et limites

Ces API accèdent directement à $4016/$4017. N’entrelacez pas sur les mêmes ports des opérations de manette, clavier ou série pilotées par interruption. Les positions de décalage des contrôleurs peuvent aussi être affectées. L’exemple désactive la NMI pendant la capture des entrées, puis l’active à l’initialisation de l’affichage.

Lit le signal instantané. Ne génère pas de strobe, n’attend pas une image, ne détecte pas de front, ne localise pas la visée et ne décide pas d’un impact. Les appels bruts et ceux de gâchette/lumière lisent chacun le registre à leur propre instant ; leurs valeurs peuvent donc différer si le signal change.

L’entrée lumineuse est active à l’état bas : le bit 3 à zéro produit un résultat de un. Cette conversion ne détecte pas la connexion d’un pistolet ; une manette ou une entrée débranchée peut aussi donner un. Dessiner une cible à l’écran n’en fait pas une API de détection d’impact.

La conversion des bits est aussi vérifiée dans une ROM de test créée pour ce projet, en redirigeant les lectures de registres vers des valeurs de test en RAM. Les cas couvrent les combinaisons 00/08/10/18 avec d’autres bits à 1. Ce test unitaire est distinct de l’exécution de l’exemple non modifié et du comportement d’un périphérique réel.

Ces images exécutent les ROM téléchargeables sans modification dans KUROSAKI. Son bus d’entrée modélise les manettes, mais pas les signaux du clavier, du pistolet optique ou d’un adaptateur série. Les bits d’entrée externes se lisent à zéro ; les captures ne valident donc pas ces appareils. Les touches physiques, la détection optique et la communication restent non testées.

NESdev: Zapper

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.

__zapper_light1: ZAPPER-DEFAULT — Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.

Programme d’exemple complet

Commande de compilation

powershell
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.png
Implémentation et source

kitaqfc/lib/intrinsics.h:247

Notes de déclaration (texte source)
Invert port-1 bit 3 and normalize: one means light detected.

kitaqfc/kitaqfc/CodeGenerator.cs:7751

csharp
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

Lit le signal lumineux du port 2 ($4017) et convertit cette entrée active à l’état bas pour renvoyer 1 lorsque de la lumière est détectée.

Syntaxe

c
u8 __zapper_light2(void);

Valeur renvoyée

1 si le bit 3 de $4017 est à 0 ; 0 s’il est à 1.

Conditions et limites

Ces API accèdent directement à $4016/$4017. N’entrelacez pas sur les mêmes ports des opérations de manette, clavier ou série pilotées par interruption. Les positions de décalage des contrôleurs peuvent aussi être affectées. L’exemple désactive la NMI pendant la capture des entrées, puis l’active à l’initialisation de l’affichage.

Lit le signal instantané. Ne génère pas de strobe, n’attend pas une image, ne détecte pas de front, ne localise pas la visée et ne décide pas d’un impact. Les appels bruts et ceux de gâchette/lumière lisent chacun le registre à leur propre instant ; leurs valeurs peuvent donc différer si le signal change.

L’entrée lumineuse est active à l’état bas : le bit 3 à zéro produit un résultat de un. Cette conversion ne détecte pas la connexion d’un pistolet ; une manette ou une entrée débranchée peut aussi donner un. Dessiner une cible à l’écran n’en fait pas une API de détection d’impact.

La conversion des bits est aussi vérifiée dans une ROM de test créée pour ce projet, en redirigeant les lectures de registres vers des valeurs de test en RAM. Les cas couvrent les combinaisons 00/08/10/18 avec d’autres bits à 1. Ce test unitaire est distinct de l’exécution de l’exemple non modifié et du comportement d’un périphérique réel.

Ces images exécutent les ROM téléchargeables sans modification dans KUROSAKI. Son bus d’entrée modélise les manettes, mais pas les signaux du clavier, du pistolet optique ou d’un adaptateur série. Les bits d’entrée externes se lisent à zéro ; les captures ne valident donc pas ces appareils. Les touches physiques, la détection optique et la communication restent non testées.

NESdev: Zapper

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.

__zapper_light2: ZAPPER-DEFAULT — Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.

Programme d’exemple complet

Commande de compilation

powershell
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.png
Implémentation et source

kitaqfc/lib/intrinsics.h:249

Notes de déclaration (texte source)
Invert port-2 bit 3 and normalize: one means light detected.

kitaqfc/kitaqfc/CodeGenerator.cs:7753

csharp
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

Lisez ensemble les deux bits du pistolet optique sur le port 1 ($4016) pour décoder la gâchette et la lumière à partir d’une même lecture du registre.

Syntaxe

c
u8 __zapper_raw1(void);

Valeur renvoyée

Renvoie $4016 & 0x18, soit 0x00, 0x08, 0x10 ou 0x18. Le bit 4 correspond à la gâchette ; le bit 3 est le signal brut d’absence de lumière.

Conditions et limites

Ces API accèdent directement à $4016/$4017. N’entrelacez pas sur les mêmes ports des opérations de manette, clavier ou série pilotées par interruption. Les positions de décalage des contrôleurs peuvent aussi être affectées. L’exemple désactive la NMI pendant la capture des entrées, puis l’active à l’initialisation de l’affichage.

Lit le signal instantané. Ne génère pas de strobe, n’attend pas une image, ne détecte pas de front, ne localise pas la visée et ne décide pas d’un impact. Les appels bruts et ceux de gâchette/lumière lisent chacun le registre à leur propre instant ; leurs valeurs peuvent donc différer si le signal change.

L’entrée lumineuse est active à l’état bas : le bit 3 à zéro produit un résultat de un. Cette conversion ne détecte pas la connexion d’un pistolet ; une manette ou une entrée débranchée peut aussi donner un. Dessiner une cible à l’écran n’en fait pas une API de détection d’impact.

La conversion des bits est aussi vérifiée dans une ROM de test créée pour ce projet, en redirigeant les lectures de registres vers des valeurs de test en RAM. Les cas couvrent les combinaisons 00/08/10/18 avec d’autres bits à 1. Ce test unitaire est distinct de l’exécution de l’exemple non modifié et du comportement d’un périphérique réel.

Ces images exécutent les ROM téléchargeables sans modification dans KUROSAKI. Son bus d’entrée modélise les manettes, mais pas les signaux du clavier, du pistolet optique ou d’un adaptateur série. Les bits d’entrée externes se lisent à zéro ; les captures ne valident donc pas ces appareils. Les touches physiques, la détection optique et la communication restent non testées.

NESdev: Zapper

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.

__zapper_raw1: ZAPPER-DEFAULT — Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.

Programme d’exemple complet

Commande de compilation

powershell
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.png
Implémentation et source

kitaqfc/lib/intrinsics.h:239

Notes de déclaration (texte source)
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

csharp
case "__zapper_raw1": if (!Need(0)) return true; EmitAsm("LDA", Mem(0x4016)); EmitAsm("AND", Imm(0x18)); return true;
__zapper_raw2

Lisez ensemble les deux bits du pistolet optique sur le port 2 ($4017) pour décoder la gâchette et la lumière à partir d’une même lecture du registre.

Syntaxe

c
u8 __zapper_raw2(void);

Valeur renvoyée

Renvoie $4017 & 0x18, soit 0x00, 0x08, 0x10 ou 0x18. Le bit 4 correspond à la gâchette ; le bit 3 est le signal brut d’absence de lumière.

Conditions et limites

Ces API accèdent directement à $4016/$4017. N’entrelacez pas sur les mêmes ports des opérations de manette, clavier ou série pilotées par interruption. Les positions de décalage des contrôleurs peuvent aussi être affectées. L’exemple désactive la NMI pendant la capture des entrées, puis l’active à l’initialisation de l’affichage.

Lit le signal instantané. Ne génère pas de strobe, n’attend pas une image, ne détecte pas de front, ne localise pas la visée et ne décide pas d’un impact. Les appels bruts et ceux de gâchette/lumière lisent chacun le registre à leur propre instant ; leurs valeurs peuvent donc différer si le signal change.

L’entrée lumineuse est active à l’état bas : le bit 3 à zéro produit un résultat de un. Cette conversion ne détecte pas la connexion d’un pistolet ; une manette ou une entrée débranchée peut aussi donner un. Dessiner une cible à l’écran n’en fait pas une API de détection d’impact.

La conversion des bits est aussi vérifiée dans une ROM de test créée pour ce projet, en redirigeant les lectures de registres vers des valeurs de test en RAM. Les cas couvrent les combinaisons 00/08/10/18 avec d’autres bits à 1. Ce test unitaire est distinct de l’exécution de l’exemple non modifié et du comportement d’un périphérique réel.

Ces images exécutent les ROM téléchargeables sans modification dans KUROSAKI. Son bus d’entrée modélise les manettes, mais pas les signaux du clavier, du pistolet optique ou d’un adaptateur série. Les bits d’entrée externes se lisent à zéro ; les captures ne valident donc pas ces appareils. Les touches physiques, la détection optique et la communication restent non testées.

NESdev: Zapper

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.

__zapper_raw2: ZAPPER-DEFAULT — Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.

Programme d’exemple complet

Commande de compilation

powershell
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.png
Implémentation et source

kitaqfc/lib/intrinsics.h:241

Notes de déclaration (texte source)
Return raw 4017 & 18 for the second port.

kitaqfc/kitaqfc/CodeGenerator.cs:4776

csharp
case "__zapper_raw2": if (!Need(0)) return true; EmitAsm("LDA", Mem(0x4017)); EmitAsm("AND", Imm(0x18)); return true;
__zapper_trigger

Fonction de lecture de gâchette par défaut, sur le port 2. Appelle la même routine que __zapper_trigger2().

Syntaxe

c
u8 __zapper_trigger(void);

Valeur renvoyée

Le bit 4 de $4017 ramené à 0 ou 1. Aucun basculement automatique vers le port 1 n’a lieu.

Conditions et limites

Ces API accèdent directement à $4016/$4017. N’entrelacez pas sur les mêmes ports des opérations de manette, clavier ou série pilotées par interruption. Les positions de décalage des contrôleurs peuvent aussi être affectées. L’exemple désactive la NMI pendant la capture des entrées, puis l’active à l’initialisation de l’affichage.

Lit le signal instantané. Ne génère pas de strobe, n’attend pas une image, ne détecte pas de front, ne localise pas la visée et ne décide pas d’un impact. Les appels bruts et ceux de gâchette/lumière lisent chacun le registre à leur propre instant ; leurs valeurs peuvent donc différer si le signal change.

Le résultat est l’état logique de la ligne de gâchette. Il ne garantit pas que la gâchette reste complètement pressée ; le comportement du commutateur dépend de l’appareil. Pour produire un tir dans le jeu, comparez avec la lecture précédente et détectez la transition voulue.

La conversion des bits est aussi vérifiée dans une ROM de test créée pour ce projet, en redirigeant les lectures de registres vers des valeurs de test en RAM. Les cas couvrent les combinaisons 00/08/10/18 avec d’autres bits à 1. Ce test unitaire est distinct de l’exécution de l’exemple non modifié et du comportement d’un périphérique réel.

Ces images exécutent les ROM téléchargeables sans modification dans KUROSAKI. Son bus d’entrée modélise les manettes, mais pas les signaux du clavier, du pistolet optique ou d’un adaptateur série. Les bits d’entrée externes se lisent à zéro ; les captures ne valident donc pas ces appareils. Les touches physiques, la détection optique et la communication restent non testées.

NESdev: Zapper

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.

__zapper_trigger: ZAPPER-DEFAULT — Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.

Programme d’exemple complet

Commande de compilation

powershell
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.png
Implémentation et source

kitaqfc/lib/intrinsics.h:251

Notes de déclaration (texte source)
Default to the port-2 normalized trigger reader.

kitaqfc/kitaqfc/CodeGenerator.cs:7749

csharp
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

Lit le signal de gâchette du port 1 ($4016) et le ramène à 0 ou 1.

Syntaxe

c
u8 __zapper_trigger1(void);

Valeur renvoyée

1 si le bit 4 de $4016 est à 1, sinon 0.

Conditions et limites

Ces API accèdent directement à $4016/$4017. N’entrelacez pas sur les mêmes ports des opérations de manette, clavier ou série pilotées par interruption. Les positions de décalage des contrôleurs peuvent aussi être affectées. L’exemple désactive la NMI pendant la capture des entrées, puis l’active à l’initialisation de l’affichage.

Lit le signal instantané. Ne génère pas de strobe, n’attend pas une image, ne détecte pas de front, ne localise pas la visée et ne décide pas d’un impact. Les appels bruts et ceux de gâchette/lumière lisent chacun le registre à leur propre instant ; leurs valeurs peuvent donc différer si le signal change.

Le résultat est l’état logique de la ligne de gâchette. Il ne garantit pas que la gâchette reste complètement pressée ; le comportement du commutateur dépend de l’appareil. Pour produire un tir dans le jeu, comparez avec la lecture précédente et détectez la transition voulue.

La conversion des bits est aussi vérifiée dans une ROM de test créée pour ce projet, en redirigeant les lectures de registres vers des valeurs de test en RAM. Les cas couvrent les combinaisons 00/08/10/18 avec d’autres bits à 1. Ce test unitaire est distinct de l’exécution de l’exemple non modifié et du comportement d’un périphérique réel.

Ces images exécutent les ROM téléchargeables sans modification dans KUROSAKI. Son bus d’entrée modélise les manettes, mais pas les signaux du clavier, du pistolet optique ou d’un adaptateur série. Les bits d’entrée externes se lisent à zéro ; les captures ne valident donc pas ces appareils. Les touches physiques, la détection optique et la communication restent non testées.

NESdev: Zapper

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.

__zapper_trigger1: ZAPPER-DEFAULT — Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.

Programme d’exemple complet

Commande de compilation

powershell
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.png
Implémentation et source

kitaqfc/lib/intrinsics.h:243

Notes de déclaration (texte source)
Return port-1 trigger bit 4 normalized to 0 or 1.

kitaqfc/kitaqfc/CodeGenerator.cs:7747

csharp
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

Lit le signal de gâchette du port 2 ($4017) et le ramène à 0 ou 1.

Syntaxe

c
u8 __zapper_trigger2(void);

Valeur renvoyée

1 si le bit 4 de $4017 est à 1, sinon 0.

Conditions et limites

Ces API accèdent directement à $4016/$4017. N’entrelacez pas sur les mêmes ports des opérations de manette, clavier ou série pilotées par interruption. Les positions de décalage des contrôleurs peuvent aussi être affectées. L’exemple désactive la NMI pendant la capture des entrées, puis l’active à l’initialisation de l’affichage.

Lit le signal instantané. Ne génère pas de strobe, n’attend pas une image, ne détecte pas de front, ne localise pas la visée et ne décide pas d’un impact. Les appels bruts et ceux de gâchette/lumière lisent chacun le registre à leur propre instant ; leurs valeurs peuvent donc différer si le signal change.

Le résultat est l’état logique de la ligne de gâchette. Il ne garantit pas que la gâchette reste complètement pressée ; le comportement du commutateur dépend de l’appareil. Pour produire un tir dans le jeu, comparez avec la lecture précédente et détectez la transition voulue.

La conversion des bits est aussi vérifiée dans une ROM de test créée pour ce projet, en redirigeant les lectures de registres vers des valeurs de test en RAM. Les cas couvrent les combinaisons 00/08/10/18 avec d’autres bits à 1. Ce test unitaire est distinct de l’exécution de l’exemple non modifié et du comportement d’un périphérique réel.

Ces images exécutent les ROM téléchargeables sans modification dans KUROSAKI. Son bus d’entrée modélise les manettes, mais pas les signaux du clavier, du pistolet optique ou d’un adaptateur série. Les bits d’entrée externes se lisent à zéro ; les captures ne valident donc pas ces appareils. Les touches physiques, la détection optique et la communication restent non testées.

NESdev: Zapper

Exemple d’utilisation

c
#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();}
}

Cet extrait montre l’appel. Avant de l’intégrer à un programme complet, respectez les conditions d’initialisation et de dimensionnement des tampons indiquées.

Résultat attendu

Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.

__zapper_trigger2: ZAPPER-DEFAULT — Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — Dans KUROSAKI, les deux ports affichent RAW=00, TRIGGER=000 et LIGHT=001. Les appels sans suffixe donnent les mêmes valeurs 000/001 que le port 2. LIGHT=001 vient du bit d’entrée 3 non émulé, lu à zéro, et non d’une détection de luminosité. L’écran précise aussi LIGHT 1 IS NOT A HIT TEST.

Programme d’exemple complet

Commande de compilation

powershell
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.png
Implémentation et source

kitaqfc/lib/intrinsics.h:245

Notes de déclaration (texte source)
Return port-2 trigger bit 4 normalized to 0 or 1.

kitaqfc/kitaqfc/CodeGenerator.cs:7749

csharp
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");
        }

Référence de syntaxe des commandes

L'aide ci-dessous a été enregistrée pour cette édition des sources. Consultez-la pour les arguments facultatifs, les valeurs par défaut et les valeurs acceptées. Remplacez les noms de fichiers illustratifs par vos propres sorties. Les réponses enregistrées des outils conservent leur texte d'origine.

kitaqfc
help
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.

Syntaxe des commandes de développement

Ces syntaxes proviennent de Program.DebugTools.cs et Program.VibeTools.cs. La comparaison de ROM, la recherche de symboles et les modèles sont des sous-commandes distinctes de la compilation C ordinaire.

usage
usage: kitaqfc kqhelp <KQxxxx>
usage
usage: kitaqfc symfind <pattern> [--map=<.map>] [--dbg=<.dbg>] [--regex] [--ignore-case] [--out=<file>|-]
usage
usage: kitaqfc src2asm <source.c:line> [--disasm=<dis.s>] [--funcsizes=<.funcsizes.txt>] [--context=N] [--out=<file>|-]
usage
usage: kitaqfc romdiff <old.nes> <new.nes> [--old-func=<.funcsizes.txt>] [--new-func=<.funcsizes.txt>] [--old-map=<.map>] [--new-map=<.map>] [--out=<file>|-]
usage
usage: kitaqfc template [out.c] [--out=<file.c>] [--overwrite] [--no-build-script] [--no-readme]
usage
usage: kitaqfc conventions [--out=<file>|--stdout]
usage
usage: kitaqfc snippet list | get <id> [--out=<file>|-] | emit <category|all> [--out=<file>|-]
usage
usage: kitaqfc fixhint [diag.json|stderr.log] [--in=<file>] [--out=<file>|-] [--max=N]
usage
usage: kitaqfc irsum [--ir=<syntax_tree_lowered.txt>] [--asm=<assembly_code.txt>] [--top=N] [--out=<file>|-]
usage
usage: kitaqfc devserver [--poll-ms=N] [--debounce-ms=N] [--once] [--max-builds=N] [--deps=<file>] <compile args...>
usage
usage: kitaqfc recipe [--history=<file>] [--last=N] [--out=<file>] [--script=<file>] [--stdout]

Options longues reconnues par l'analyseur

Les noms ont été extraits de src/cli.rs, y compris les options de compatibilité et d'investigation. Consultez l'aide et le gestionnaire correspondant pour déterminer si chaque option reçoit une valeur.

--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-alloc

Annexe : index des instructions assembleur

Ce tableau est extrait d'src/machine/opcodes.rs. Il décrit les noms internes du compilateur et les formats d'opérandes. Les entrées contenant des destinations de branchement ou des adresses mémoire sont des exemples de syntaxe, pas des programmes autonomes. Consultez les conventions d'appel et la génération de code pour les registres préservés et les changements d'indicateurs.

opcodeInstructionFormatExemple de syntaxe
00NOPIMPNOP
F3DIIMPDI
FBEIIMPEI
76HALTIMPHALT
10STOPIMM8STOP #1
C9RETIMPRET
D9RETIIMPRETI
C3JPABSJP 0xC000
E9JP_HLIMPJP_HL
18JRRELJR +target
20JR_NZRELJR_NZ +target
28JR_ZRELJR_Z +target
30JR_NCRELJR_NC +target
38JR_CRELJR_C +target
CDCALLABSCALL 0xC000
C4CALL_NZABSCALL_NZ 0xC000
CCCALL_ZABSCALL_Z 0xC000
D4CALL_NCABSCALL_NC 0xC000
DCCALL_CABSCALL_C 0xC000
C2JP_NZABSJP_NZ 0xC000
CAJP_ZABSJP_Z 0xC000
D2JP_NCABSJP_NC 0xC000
DAJP_CABSJP_C 0xC000
C7RST_00IMPRST_00
CFRST_08IMPRST_08
D7RST_10IMPRST_10
DFRST_18IMPRST_18
E7RST_20IMPRST_20
EFRST_28IMPRST_28
F7RST_30IMPRST_30
FFRST_38IMPRST_38
3ELD_A_IMMIMM8LD_A_IMM #1
06LD_B_IMMIMM8LD_B_IMM #1
0ELD_C_IMMIMM8LD_C_IMM #1
16LD_D_IMMIMM8LD_D_IMM #1
1ELD_E_IMMIMM8LD_E_IMM #1
26LD_H_IMMIMM8LD_H_IMM #1
2ELD_L_IMMIMM8LD_L_IMM #1
7FLD_A_AIMPLD_A_A
78LD_A_BIMPLD_A_B
79LD_A_CIMPLD_A_C
7ALD_A_DIMPLD_A_D
7BLD_A_EIMPLD_A_E
7CLD_A_HIMPLD_A_H
7DLD_A_LIMPLD_A_L
47LD_B_AIMPLD_B_A
40LD_B_BIMPLD_B_B
41LD_B_CIMPLD_B_C
42LD_B_DIMPLD_B_D
43LD_B_EIMPLD_B_E
44LD_B_HIMPLD_B_H
45LD_B_LIMPLD_B_L
4FLD_C_AIMPLD_C_A
48LD_C_BIMPLD_C_B
49LD_C_CIMPLD_C_C
4ALD_C_DIMPLD_C_D
4BLD_C_EIMPLD_C_E
4CLD_C_HIMPLD_C_H
4DLD_C_LIMPLD_C_L
57LD_D_AIMPLD_D_A
50LD_D_BIMPLD_D_B
51LD_D_CIMPLD_D_C
52LD_D_DIMPLD_D_D
53LD_D_EIMPLD_D_E
54LD_D_HIMPLD_D_H
55LD_D_LIMPLD_D_L
5FLD_E_AIMPLD_E_A
58LD_E_BIMPLD_E_B
59LD_E_CIMPLD_E_C
5ALD_E_DIMPLD_E_D
5BLD_E_EIMPLD_E_E
5CLD_E_HIMPLD_E_H
5DLD_E_LIMPLD_E_L
67LD_H_AIMPLD_H_A
60LD_H_BIMPLD_H_B
61LD_H_CIMPLD_H_C
62LD_H_DIMPLD_H_D
63LD_H_EIMPLD_H_E
64LD_H_HIMPLD_H_H
65LD_H_LIMPLD_H_L
6FLD_L_AIMPLD_L_A
68LD_L_BIMPLD_L_B
69LD_L_CIMPLD_L_C
6ALD_L_DIMPLD_L_D
6BLD_L_EIMPLD_L_E
6CLD_L_HIMPLD_L_H
6DLD_L_LIMPLD_L_L
EALD_MEM_AABSLD_MEM_A 0xC000
FALD_A_MEMABSLD_A_MEM 0xC000
E0LDH_MEM_ALDHLDH_MEM_A 0x40
F0LDH_A_MEMLDHLDH_A_MEM 0x40
E2LD_C_MEM_AIMPLD_C_MEM_A
F2LD_A_MEM_CIMPLD_A_MEM_C
77LD_HL_AIMPLD_HL_A
7ELD_A_HLIMPLD_A_HL
70LD_HL_BIMPLD_HL_B
71LD_HL_CIMPLD_HL_C
72LD_HL_DIMPLD_HL_D
73LD_HL_EIMPLD_HL_E
74LD_HL_HIMPLD_HL_H
75LD_HL_LIMPLD_HL_L
36LD_HL_REF_IMMIMM8LD_HL_REF_IMM #1
12LD_DE_AIMPLD_DE_A
1ALD_A_DEIMPLD_A_DE
02LD_BC_AIMPLD_BC_A
0ALD_A_BCIMPLD_A_BC
22LDI_HL_AIMPLDI_HL_A
2ALDI_A_HLIMPLDI_A_HL
32LDD_HL_AIMPLDD_HL_A
3ALDD_A_HLIMPLDD_A_HL
01LD_BC_IMMIMM16LD_BC_IMM #0xC000
11LD_DE_IMMIMM16LD_DE_IMM #0xC000
21LD_HL_IMMIMM16LD_HL_IMM #0xC000
31LD_SP_IMMIMM16LD_SP_IMM #0xC000
F9LD_SP_HLIMPLD_SP_HL
F8LD_HL_SP_IMMRELLD_HL_SP_IMM +target
E8ADD_SP_IMMRELADD_SP_IMM +target
08LD_MEM_SPIMM16LD_MEM_SP #0xC000
C5PUSH_BCIMPPUSH_BC
C1POP_BCIMPPOP_BC
D5PUSH_DEIMPPUSH_DE
D1POP_DEIMPPOP_DE
E5PUSH_HLIMPPUSH_HL
E1POP_HLIMPPOP_HL
F5PUSH_AFIMPPUSH_AF
F1POP_AFIMPPOP_AF
3CINC_AIMPINC_A
04INC_BIMPINC_B
0CINC_CIMPINC_C
14INC_DIMPINC_D
1CINC_EIMPINC_E
24INC_HIMPINC_H
2CINC_LIMPINC_L
34INC_HL_REFIMPINC_HL_REF
3DDEC_AIMPDEC_A
05DEC_BIMPDEC_B
0DDEC_CIMPDEC_C
15DEC_DIMPDEC_D
1DDEC_EIMPDEC_E
25DEC_HIMPDEC_H
2DDEC_LIMPDEC_L
35DEC_HL_REFIMPDEC_HL_REF
87ADD_AIMPADD_A
80ADD_BIMPADD_B
81ADD_CIMPADD_C
82ADD_DIMPADD_D
83ADD_EIMPADD_E
84ADD_HIMPADD_H
85ADD_LIMPADD_L
86ADD_HL_REFIMPADD_HL_REF
C6ADD_A_IMMIMM8ADD_A_IMM #1
8FADC_AIMPADC_A
88ADC_BIMPADC_B
89ADC_CIMPADC_C
8AADC_DIMPADC_D
8BADC_EIMPADC_E
8CADC_HIMPADC_H
8DADC_LIMPADC_L
8EADC_HL_REFIMPADC_HL_REF
CEADC_IMMIMM8ADC_IMM #1
97SUB_AIMPSUB_A
90SUB_BIMPSUB_B
91SUB_CIMPSUB_C
92SUB_DIMPSUB_D
93SUB_EIMPSUB_E
94SUB_HIMPSUB_H
95SUB_LIMPSUB_L
96SUB_HL_REFIMPSUB_HL_REF
D6SUB_IMMIMM8SUB_IMM #1
9FSBC_AIMPSBC_A
98SBC_BIMPSBC_B
99SBC_CIMPSBC_C
9ASBC_DIMPSBC_D
9BSBC_EIMPSBC_E
9CSBC_HIMPSBC_H
9DSBC_LIMPSBC_L
9ESBC_HL_REFIMPSBC_HL_REF
DESBC_IMMIMM8SBC_IMM #1
A7AND_AIMPAND_A
A0AND_BIMPAND_B
A1AND_CIMPAND_C
A2AND_DIMPAND_D
A3AND_EIMPAND_E
A4AND_HIMPAND_H
A5AND_LIMPAND_L
A6AND_HL_REFIMPAND_HL_REF
E6AND_IMMIMM8AND_IMM #1
B7OR_AIMPOR_A
B0OR_BIMPOR_B
B1OR_CIMPOR_C
B2OR_DIMPOR_D
B3OR_EIMPOR_E
B4OR_HIMPOR_H
B5OR_LIMPOR_L
B6OR_HL_REFIMPOR_HL_REF
F6OR_IMMIMM8OR_IMM #1
AFXOR_AIMPXOR_A
A8XOR_BIMPXOR_B
A9XOR_CIMPXOR_C
AAXOR_DIMPXOR_D
ABXOR_EIMPXOR_E
ACXOR_HIMPXOR_H
ADXOR_LIMPXOR_L
AEXOR_HL_REFIMPXOR_HL_REF
EEXOR_IMMIMM8XOR_IMM #1
BFCP_AIMPCP_A
B8CP_BIMPCP_B
B9CP_CIMPCP_C
BACP_DIMPCP_D
BBCP_EIMPCP_E
BCCP_HIMPCP_H
BDCP_LIMPCP_L
BECP_HL_REFIMPCP_HL_REF
FECP_IMMIMM8CP_IMM #1
03INC_BCIMPINC_BC
0BDEC_BCIMPDEC_BC
13INC_DEIMPINC_DE
1BDEC_DEIMPDEC_DE
23INC_HLIMPINC_HL
2BDEC_HLIMPDEC_HL
33INC_SPIMPINC_SP
3BDEC_SPIMPDEC_SP
09ADD_HL_BCIMPADD_HL_BC
19ADD_HL_DEIMPADD_HL_DE
29ADD_HL_HLIMPADD_HL_HL
39ADD_HL_SPIMPADD_HL_SP
07RLCAIMPRLCA
17RLAIMPRLA
0FRRCAIMPRRCA
1FRRAIMPRRA
CBPREFIX_CBIMPPREFIX_CB
2FCPLIMPCPL
3FCCFIMPCCF
37SCFIMPSCF
27DAAIMPDAA