KITAQ SISTEMA DE DESARROLLO
MANUAL DE USO 2026.09

SERIE KITAQ • EDICIÓN DE REFERENCIA

KITAQFC

Programación en C para Famicom

Desde tu primera línea hasta la ejecución, la observación y la repetición de pruebas.
Código fuente recopilado: 14 de septiembre de 2026 · Edición inglesa: 14 de septiembre de 2026

Compilador nativo Rust y herramientas auxiliares

Con Rust 1.85 o posterior puede compilar el compilador y todas las herramientas para Windows, Linux, macOS ARM y macOS Intel. Los ejecutables nativos no necesitan .NET; las herramientas de recursos tampoco requieren Python ni Pillow.

Los ejecutables de Windows están en la raíz del repositorio; los de Linux y macOS, en los directorios bin/ de la tabla siguiente. Conserve lib/ y los avisos de licencia junto a las herramientas. En Linux/macOS use chmod +x y añada el directorio al PATH, o ejecute mediante la ruta completa.

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

kitaqfc · Compilador nativo Rust y herramientas auxiliares · GitHub Actions

Las compilaciones nativas y comprobaciones de ejecución finalizaron correctamente en Windows, Linux, macOS ARM y macOS Intel. KITAQGB superó 48 pruebas y 395 comprobaciones de herramientas por entorno; KITAQFC, 55 y 401. También se probó Rust 1.85. PUBLIC_DISTRIBUTION.json registra los hashes de los binarios instalados y el origen de la validación. Los flujos públicos de GitHub Actions compilan y prueban estas fuentes de forma independiente.

Las salidas de referencia guardadas comprueban bytes de ROM, diagnósticos y formatos de las herramientas. Las anteriores pruebas de emulación C# son registros históricos vinculados a sus fuentes originales. No demuestran automáticamente todas las API Rust, el hardware real ni el arranque completo de juegos mediante el BIOS FDS. El script PNG original no está disponible y se reconstruyó a partir de su especificación; no puede afirmarse igualdad de bytes con él.

--attach se acepta por compatibilidad, pero no conecta un depurador administrado al ejecutable nativo. Use un depurador nativo para el programa de PC.

KITAQGB · SHA-256 · KITAQFC · SHA-256

Compilación de ejemplos en cada sistema

Los scripts PowerShell usan el ejecutable Windows de la raíz. En Linux/macOS pase las mismas entradas C y opciones al compilador nativo, o use PowerShell 7. Los gráficos CHR y el código C son entradas distintas; font.chr conserva la fuente de los ejemplos.

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

Distribución actual del compilador y las bibliotecas — 4 de octubre de 2026

Use kitaqfc.exe y lib de la misma copia del repositorio. Desde el directorio padre, ejecute el script Release indicado abajo; el proyecto copia el ejecutable a la raíz del repositorio. BINARY_BUILD.json registra los hashes de las fuentes y del ejecutable, y las comprobaciones de esa compilación. Las líneas de diagnóstico largas se limitan a 200 caracteres y puntos suspensivos.

Se comparó la compilación C# actual con la distribución anterior mediante 29 entradas. Quince entradas aceptadas generaron los mismos bytes ROM; las otras catorce produjeron los mismos diagnósticos de rechazo. Dos ROM adicionales se ejecutaron durante cuatro fotogramas en KUROSAKI: asignación de entidades, llamadas y reutilización de posiciones; aritmética con signo, disposición de KQBody3D y constantes de la cola de audio. El informe recoge 18 bytes RAM esperados y observados. Estas pruebas no comprueban la reproducción de audio ni todas las API; las regresiones anteriores se identifican por separado en BINARY_BUILD.json.

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

Ejemplos fuente y pruebas de ejecución

1. KITAQFC y el compilador de GB

KITAQFC utiliza el front-end de KITAQGB para generar código para la CPU de la familia 6502 de NES/Famicom. No convierte una ROM de GB en una de NES. El programa debe diseñarse para la pantalla, el sonido, la memoria y el mapper de su destino.

KITAQFC admite copias de estructuras, llamadas normales a funciones y bucles for, while y do-while. Un do-while ejecuta el cuerpo al menos una vez antes de comprobar la condición. continue pasa a esa comprobación final; break sale del bucle. switch selecciona una constante case entre 0 y 255, o el cuerpo de default si no hay coincidencia. La expresión de selección se evalúa una sola vez. break sale del bucle o switch más interno; continue dentro de un switch pasa a la siguiente iteración del bucle que lo contiene.

2. Requisitos y compilación

Con Rust 1.85 o posterior puede compilar el compilador y todas las herramientas para Windows, Linux, macOS ARM y macOS Intel. Los ejecutables nativos no necesitan .NET; las herramientas de recursos tampoco requieren Python ni Pillow.

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

Los ejecutables de Windows están en la raíz del repositorio; los de Linux y macOS, en los directorios bin/ de la tabla siguiente. Conserve lib/ y los avisos de licencia junto a las herramientas. En Linux/macOS use chmod +x y añada el directorio al PATH, o ejecute mediante la ruta completa.

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

Los scripts PowerShell usan el ejecutable Windows de la raíz. En Linux/macOS pase las mismas entradas C y opciones al compilador nativo, o use PowerShell 7. Los gráficos CHR y el código C son entradas distintas; font.chr conserva la fuente de los ejemplos.

3. Tu primer programa

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

El programa muestra HELLO WORLD y 042. m_wait confirma la cola de VRAM, espera la NMI y restaura el desplazamiento de pantalla. Las transferencias mediante PPUADDR alteran el estado interno de desplazamiento; no restaurarlo puede mover el texto hacia un borde. Aprende esta secuencia: apagar la imagen, preparar recursos, encenderla y sincronizar con NMI.

4. Introducción al lenguaje

Las sentencias y expresiones del volumen GB son una base común. FC acepta unsigned char y unsigned short; core.h define u8, u16, s8 y s16. fc.h reúne los encabezados. Aquí sí puedes declarar una entrada sin argumentos como void main(void).

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

Mantén los enteros dentro de sus rangos de 8 o 16 bits. Los índices empiezan en cero. fc_aggregate.c enseña funciones, punteros y estructuras; fc_arithmetic.c, aritmética; fc_control.c, bucles. No incluyas registros CGB ni operaciones exclusivas de GB.

Resultados de las expresiones y evaluación

Los operadores de comparación ==, !=, <, <=, >, >= y los operadores lógicos !, &&, || devuelven 0 para falso y 1 para verdadero. Puedes guardar el resultado en un u16, pasarlo como argumento, devolverlo o usarlo en operaciones aritméticas. Por ejemplo, score = 500 + (lives != 0); produce 501 si queda alguna vida y 500 en caso contrario.

&& omite la evaluación del operando derecho cuando el izquierdo es cero. || la omite cuando el izquierdo no es cero. En pointer != 0 && pointer->active != 0, un puntero nulo impide el acceso al miembro. Los dos bytes de un valor de 16 bits intervienen en la comprobación lógica, por lo que 256 es verdadero. Los operadores de bits & y | no realizan evaluación en cortocircuito.

++value devuelve el valor actualizado; value++, el valor original. Estos operadores también admiten elementos de arrays, punteros desreferenciados y miembros de estructuras. buffer[index()]++ llama a index() una sola vez. Para u16 *p, p++ avanza dos bytes hasta el siguiente elemento, mientras que (*p)++ incrementa el valor apuntado.

sizeof(array) da el tamaño en bytes del array completo; sizeof(pointer) es 2. Para u16 values[9];, sizeof(values) es 18. Esto incluye arrays en ROM, arrays locales y miembros de tipo array. sizeof(function()) examina el tipo de retorno sin llamar a la función.

La declaración y la definición de una función deben coincidir en los tipos y el orden de los parámetros. Sus nombres pueden ser distintos; el cuerpo usa los de la definición. Por ejemplo, u8 next(u8 input); puede definirse como u8 next(u8 value) { return (u8)(value + 1); }. Los parámetros y las variables locales ocultan las variables globales con el mismo nombre.

Seleccionar arrays o cadenas con ?: produce un puntero al tipo de elemento seleccionado. Puedes pasarlo directamente, como en show(ready ? "READY" : "WAIT");. Para los arrays u16 llamados a y b, (ready ? a : b) + 1 avanza dos bytes hasta el segundo elemento del array elegido. No copia el array.

condition ? yes : no evalúa la condición y después solo la rama seleccionada. La condición y las ramas pueden incluir desplazamientos con cantidades de bits variables. Por ejemplo, on = (pattern & (0x80 >> bit)) != 0 ? 4 : 2; elige cuatro o dos según el bit indicado. Tampoco se ejecutan las llamadas a funciones de un operando derecho omitido por && o ||. Un continue en un bucle for ejecuta una vez la expresión de actualización antes de reevaluar la condición; en while y do ... while, pasa a la condición.

5. Bucles y selección de acciones según el estado

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

Estos fragmentos muestran un bucle que actualiza al menos una vez y una bifurcación que elige el manejador del estado actual. Define update, condition, state y los manejadores de estado en tu programa. Consulta fc_control.c para un ejemplo completo de bucle y el ejemplo de fotogramas y escenas de la biblioteca para una ROM completa de gestión de escenas.

Registra los callbacks de escenas, entidades y sistema con los tipos de argumentos y retorno que exigen sus declaraciones. Las bibliotecas invocan los manejadores registrados desde las operaciones correspondientes de actualización, dibujo o espera de fotograma. Respeta los requisitos de bancos ROM y mapeo de cada API. No presupongas el mismo soporte para recursión o funciones variádicas que en un equipo de escritorio.

6. Memoria y PPU

La RAM interna de CPU ocupa 0x0000–0x07FF. Sus réplicas por encima de 0x0800 no son RAM adicional. La pila del 6502 usa la página 1; el OAM en RAM y las colas reservan otras zonas. --nes-local-ram=START:LENGTH y --nes-temp-ram=START:LENGTH son ajustes avanzados que requieren revisar el mapa.

La PPU tiene su propio espacio de direcciones. CHR contiene patrones; las nametables colocan tiles; las tablas de atributos eligen grupos de paletas; las paletas contienen códigos de color. Los atributos de fondo suelen aplicarse a zonas de 16×16 píxeles, no como los atributos de tile de GB.

7. NMI y cola de VRAM

La NMI es la interrupción asociada al límite de cada fotograma. Las escrituras directas grandes en PPU durante el renderizado pueden corromper la pantalla. Inicializa directamente con la imagen apagada; para actualizaciones normales usa __vramq_put, __vramq_copy, __vramq_fill y la confirmación de la cola.

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

Comprueba la capacidad y la vida útil de los datos de origen. La NMI predeterminada procesa la cola. Un __nes_nmi propio debe conservar la ejecución de cola necesaria, el trabajo de OAM y los registros requeridos.

8. Mappers y distribución de ROM

SelecciónUso inicial habitual
nromLecciones pequeñas con ROM fija
uxrom / cnrom / axromCambio sencillo de PRG o CHR
mmc1 / mmc3 / mmc5Programas mayores y funciones propias del mapper
vrc6 / vrc7 / fme7Bancos y expansiones correspondientes
fdsSalida de imagen de disco

Son selecciones del compilador, no una tabla de compatibilidad completa de hardware o emuladores. --board=surom512 elige una configuración concreta de placa MMC1. Rellenar un archivo hasta 512 KiB no establece esa configuración. Acompáñalo de la auditoría de placa 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

Revisa los requisitos de --battery / --no-battery, capacidad CHR, distribución PRG y llamadas entre bancos. Tras cambiar de mapper o modo de mirroring, prueba el arranque, el desplazamiento y el acceso a bancos, además de generar la ROM.

8.1 CHR RAM para gráficos modificables

--nes-chr-ram genera una ROM de cartucho que utiliza 8 KiB de CHR RAM escribible. El archivo ROM no almacena datos CHR, por lo que el programa debe cargar los patrones de los tiles en PPU durante la inicialización. El renderizador wire3d.c utiliza este modo.

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 o --chr-rom=tiles.chr incorpora patrones preparados como CHR ROM. Ninguna de estas dos opciones puede combinarse con --nes-chr-ram. El perfil CNROM tampoco admite el modo CHR RAM. Compruebe que el mapper y la placa de destino dispongan de la CHR RAM necesaria.

Si omite todas las opciones CHR, el compilador incorpora una CHR ROM vacía de 8 KiB. Seleccione explícitamente --nes-chr-ram en los ejemplos que cargan patrones durante la ejecución. CHR RAM es independiente de la PRG RAM de CPU; el búfer de preparación de gráficos alámbricos también necesita su propia asignación de PRG RAM.

Las lecturas normales por índice y las referencias mediante punteros de C a arrays en ROM colocan esos arrays en el banco común 0, salvo que #pragma fixed_bank fije explícitamente su ubicación. Así, los datos siguen siendo visibles cuando quien los usa se ejecuta en un banco conmutable. Para un acceso lejano explícito, pasa en la misma llamada el nombre del array sin modificar y __bankof aplicado a ese mismo nombre, por ejemplo __farpeek8(__bankof(table), table). Consultar únicamente el número de banco no solicita la ubicación en el banco común. Los datos fijados explícitamente a un banco y las referencias dentro de ensamblador requieren que mantengas el mapeo correcto. Esta regla comprueba la ubicación según la sintaxis, no mediante un análisis del flujo de punteros. Sigue aplicándose el límite de capacidad del banco común.

9. FDS, sonido de expansión y periféricos

FDS requiere distribuir archivos de disco y planificar arranque, overlays y guardado. Consulta fds_manifest_sample.json y los encabezados FDS. Si necesitas una BIOS, debes disponer de ella en tu entorno de ejecución: no se incluye en la distribución pública.

Llamar a una operación de sonido VRC6 o VRC7 no cambia el mapper de la ROM. Ambos deben corresponderse. Para periféricos, prueba por separado la entrada legible, la conexión y sus efectos sobre la lectura del mando normal.

10. Diagnósticos y resultados

Los diagnósticos KQ y comandos como symfind, src2asm y romdiff se parecen a los de GB. Algunas opciones heredadas de su ayuda pueden no estar implementadas para NES. El diccionario FC se extrae de sus propias fuentes y encabezados.

Avisos de acceso directo a PPU, como KQ2421, pueden aparecer incluso al inicializar con la pantalla apagada. No rompas una inicialización segura solo para eliminar un aviso: revisa tiempos de renderizado y registros. Cero errores y cero avisos son resultados distintos.

11. Ejemplo práctico: desarrollar un juego con IA

Completa los requisitos y entrega el prompt íntegro a tu asistente de IA. Incluye implementación, pruebas en el emulador, análisis con SARAKURA y verificación de las correcciones.

· Descargar Markdown

Completa los requisitos y entrega este documento íntegro al asistente de IA. Los comandos suponen que los repositorios kitaqgb, kitaqfc, kokura, kurosaki, sarakura y kitaq-docs, junto con el proyecto game-gb o game-fc, comparten un directorio padre. Ejecútalos desde ese directorio y adapta las rutas al entorno real.

Requisitos

  • Título del juego: <completar>
  • Género y mecánica principal: <completar>
  • Controles y condiciones de éxito y fracaso: <completar>
  • Pantallas, niveles, enemigos y objetos obligatorios: <completar>
  • Estilo visual, música y efectos de sonido: <completar e indicar los recursos proporcionados>
  • Guardado, comunicación, periféricos y otros requisitos: <completar o ninguno>
  • Directorio del proyecto: <completar>
  • Condiciones de redistribución: <por ejemplo, código y recursos originales aptos para publicarse con licencia MIT>
  • Destino: <cartucho NES/Famicom o FDS>
  • Mapper, tamaño de ROM y mirroring: <especificar o elegir según los requisitos>
  • Norma de vídeo y rendimiento: <por ejemplo, NTSC y 60 actualizaciones de la lógica por segundo>

Trabajo solicitado

Implementa el juego con KITAQFC y sus bibliotecas. Utiliza KUROSAKI para ejecutar y depurar, y SARAKURA para organizar los diagnósticos y comparar los resultados antes y después de una corrección.

Repite este ciclo hasta cumplir los criterios de aceptación: concretar la especificación → implementar un cambio pequeño → compilar → aplicar entradas y observar → investigar la causa → corregir → repetir las pruebas en las mismas condiciones. Un plan, un listado de código o una compilación correcta no bastan para dar el trabajo por terminado.

Comprobar el entorno y los criterios de aceptación

  1. Lee las instrucciones del directorio de trabajo, los README, los manuales HTML y las cabeceras e implementaciones de las bibliotecas que vayas a usar. Registra las rutas de los ejecutables y sus versiones o hashes SHA-256. Comprueba los comandos con la salida real de --help y las API con el código fuente.
  2. Define criterios verificables para entradas, imagen, sonido, progreso y frecuencia de actualización. Por ejemplo: pulsar y soltar START inicia la partida; una colisión resta una vida; la pausa silencia el audio indicado y al continuar se reanuda la reproducción.
  3. Pregunta solo por ambigüedades importantes. Resuelve de forma autónoma las decisiones habituales y reversibles. No rebajes los requisitos ni los criterios de aceptación.
  4. Primero ejecuta un pequeño ejemplo incluido con el compilador, el emulador y SARAKURA. Esto comprueba la conexión entre herramientas, no la finalización del juego solicitado.

Implementar una primera versión jugable

  • Considera NROM para un juego pequeño y elige MMC3 u otro mapper cuando el tamaño o los cambios de banco lo requieran. Comprueba las funciones necesarias de la placa con inspect-rom, mapper-info, audit-board y su implementación; el nombre del mapper no demuestra que estén soportadas.
  • Planifica tamaños PRG/CHR, CHR-ROM o CHR-RAM, mirroring, bancos fijos, vectores de interrupción y RAM de guardado. Tras optimizar o cambiar bancos, compara la cabecera con la distribución real. --nes-local-ram utiliza la RAM interna de CPU $0000–$07FF; evita solapamientos con página cero, pila, búferes OAM y áreas del runtime o las bibliotecas.
  • Utiliza el dialecto C de KITAQFC, las bibliotecas FC y void main(void). No supongas compatibilidad con las API de GB. Algunas entradas de las cabeceras son solo declaraciones: localiza las implementaciones e incluye los .c necesarios.
  • Ten en cuenta registros PPU, NMI, OAM DMA, límite de sprites por línea, desplazamiento, mirroring, tablas de atributos y APU/DMC. El espacio libre de la cola no es la capacidad de VRAM del PPU; limita el trabajo por NMI.
  • Convierte la fuente original ascii.c a CHR de FC y verifica CHR, paletas, tablas de nombres y atributos. Para FDS, comprueba por separado acceso al disco, guardado y requisitos de BIOS; no presupongas el arranque de un cartucho.
  • Conecta primero arranque, título, personaje controlable, éxito o fracaso y reinicio. Amplía el contenido después.
  • Conserva los originales editables de gráficos, música y efectos, así como los pasos de generación. Comprueba que la compilación consume realmente los datos exportados.
  • Escribe los comentarios del código en inglés y los informes de progreso en español. Mantén los informes estándar de SARAKURA en inglés.

Vincular cada compilación con su ejecución

Separa las salidas por iteración, por ejemplo en out/iter-001. Registra comandos, códigos de salida y hashes de código, recursos, herramientas, ROM y metadatos. Nunca ejecutes una ROM anterior después de una compilación fallida. Los mapas, mapas de código fuente y datos de depuración deben corresponder a la misma compilación que la ROM.

Este ejemplo comprueba NROM sin entrada. Prepara main.c, las implementaciones necesarias y el archivo CHR; elige el mapper adecuado. No pases los metadatos de compilación a --kitaqfc-debug de KUROSAKI sin comprobar primero el formato requerido.

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

Una ejecución de 300 fotogramas sin entrada es solo una comprobación inicial. Añade escenarios de juego con secuencias de acciones antes de afirmar que funciona.

Reproducir secuencias de entrada

  • --pad1 y --pad2 utilizan máscaras de bits NES sin convertir: A=1, B=2, SELECT=4, START=8, UP=16, DOWN=32, LEFT=64, RIGHT=128. No las confundas con los valores BTN_* de la biblioteca.
  • run --pad1 aplica una entrada fija. Para acciones sucesivas, crea una reproducción que distinga pulsación, mantenimiento y liberación. Consulta Replay y ReplayFrame. En la CLI pública, replay-record registra entrada neutra, no una partida jugada por una persona.
  • Verifica tú mismo el SHA-256 de la ROM, el destino de la reproducción y el intervalo de fotogramas. replay-run --verify solo compara el hash final esperado cuando existe; no valida exhaustivamente el juego ni la identidad de la ROM. No sustituyas sin más los resultados esperados por los observados para que una prueba pase.
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'

Prepara la reproducción para la ROM que vas a probar. La CLI pública no admite --emit-diagnostics en replay-run. No inventes esa opción ni uses trazas CPU como eventos de diagnóstico. Para analizar secuencias de entrada con SARAKURA, crea un programa de pruebas dentro del proyecto con las API públicas de kurosaki-core: RunOptions.replay_frames y diagnostic_events_from_trace_and_report. Ejecuta la misma ROM, reproducción y condiciones de fotogramas; genera JSONL a partir de la traza y el informe diagnóstico de esa ejecución. Revisa la configuración y el intervalo conservado de la traza, y compara imágenes y observaciones del programa de pruebas con la reproducción de la CLI. No presentes diagnósticos sin entrada como evidencia de una partida. Si falta el entorno necesario, declara esta verificación incompleta.

Comprobar imagen, sonido, estado y rendimiento

  • Guarda escenarios que distingan pulsar, mantener y soltar. Recorre todas las rutas especificadas: arranque, inicio, movimiento, acciones, colisiones, desplazamiento, cambios de nivel, fin de partida, reinicio, pausa y, cuando corresponda, guardado o comunicación.
  • Conserva PNG de fotogramas relevantes, entradas, informes de ejecución, JSONL de diagnóstico, WAV y las observaciones necesarias de estado o memoria. Comprueba los fotogramas alcanzados y el motivo de parada. Abre las imágenes: una sola captura no demuestra movimiento ni respuesta a los controles. Compara contadores, posiciones y cambios de estado con lo esperado; revisa bordes de pantalla, límites de tiles y atributos, y escenas con muchos sprites.
  • Comprueba música, efectos, reproducción simultánea, cortes, pausa y reanudación. Crear un WAV no demuestra que el sonido sea correcto. Si no puedes escucharlo, distingue las comprobaciones numéricas o de forma de onda de las cualidades audibles aún sin verificar.
  • Mide escenas exigentes, trabajo de la CPU de destino, actualizaciones y transferencias; en FC, incluye el trabajo de NMI. La velocidad del emulador en el equipo anfitrión no es la frecuencia del juego ni prueba la velocidad en hardware real. Continuar con --allow-unimplemented no demuestra soporte para la función ausente.

Analizar, corregir y volver a probar

  • Proporciona a SARAKURA los metadatos de la ROM probada y el JSONL de diagnóstico de esa ejecución. Una traza CPU o un informe ordinario no los sustituyen. --frames establece condiciones de análisis; SARAKURA no ejecuta la ROM ni modifica automáticamente el código.
  • Lee report.html, ai_diagnostics.json, repair_prompt.md y retest_plan.json. Contrasta los diagnósticos con reproducción, imágenes, audio y código. Distingue ubicaciones o causas inferidas de hechos comprobados, y bucles de espera normales de bloqueos. Evalúa las advertencias una por una y registra eventos no soportados o límites del análisis. No ocultes advertencias con filtros ni acortes las pruebas para conseguir un resultado favorable.
  • Reduce cada fallo a un caso mínimo, corrige su causa y recompila. Si procede del compilador o emulador, aísla el defecto del código del juego y añade comprobaciones de regresión para la corrección de la herramienta.
  • Repite las pruebas con las mismas entradas, semilla aleatoria, máquina y norma de vídeo, mapper, fotogramas observados y ajustes de diagnóstico. Cada ROM requiere sus metadatos; no reutilices estados guardados a ciegas tras cambiar código o distribución de 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

Usa las diferencias de diagnóstico junto con la aceptación de controles, gráficos y audio. Si el mismo fallo se repite, revisa las pruebas y la hipótesis en lugar de encadenar cambios arbitrarios.

Criterios de finalización y entregables

Repite todos los escenarios obligatorios con la ROM final compilada a partir del código y los ajustes entregados. La invencibilidad, entradas automáticas de prueba u otro mapper, por sí solos, no verifican una partida normal en la versión final. Entrega una tabla de requisitos y pruebas, explica las advertencias restantes e identifica lo no comprobado o no soportado. Si no se ha probado en hardware físico, indícalo explícitamente.

Entrega código fuente, identificación de herramientas y bibliotecas, recursos editables, scripts reproducibles de compilación y pruebas, ROM, evidencia final y un README de instalación, controles y limitaciones conocidas. Incluye reproducciones y el programa de pruebas cuando sean necesarios. Publica o envía archivos al exterior solo dentro del alcance autorizado expresamente. Elimina compilaciones intermedias y trazas temporales innecesarias tras verificarlas, conservando fuentes, recursos, entregables y evidencia de regresión necesaria.

Si el entorno o los permisos impiden una comprobación obligatoria, comunica los pasos exactos de reproducción y la acción necesaria. No des el trabajo por terminado.

Ubicación del código fuente

El código fuente del compilador y el archivo de proyecto están en el subdirectorio que lleva el nombre del repositorio. El ejecutable está en la raíz y las bibliotecas, en lib/. Consulta la estructura de directorios para conocer las rutas y los requisitos de compilación.

Programas de ejemplo completos

Se incluyen todos los programas, las cabeceras compartidas, el archivo ascii.c suministrado y sus recursos convertidos. Los nombres que empiezan por m_ son funciones auxiliares de estos manuales. Lee cada comando de compilación junto a su resultado esperado y su registro de verificación. Ejecuta los comandos desde la carpeta que contiene los repositorios hermanos.

Script de compilación por lotes / Resultados y capturas de pantalla

Mostrar el juego de caracteres incluido fc_font.c

Objetivo: Muestra qué letras, dígitos y signos de puntuación contiene la fuente original incluida y cómo dibujarlos.

Funcionamiento: m_init prepara la pantalla y la fuente. m_text dibuja cada línea; m_put dibuja las comillas dobles mediante el código de carácter 34.

Controles y resultado esperado: Busque 26 letras mayúsculas, 26 minúsculas, 10 dígitos y 30 signos de puntuación. Compruebe que todos los glifos sean legibles. No se requiere ninguna entrada.

Mostrar el juego de caracteres incluido: Busque 26 letras mayúsculas, 26 minúsculas, 10 dígitos y 30 signos de puntuación. Compruebe que todos los glifos sean legibles. No se requiere ninguna entrada.
Ejemplo en pantalla: Busque 26 letras mayúsculas, 26 minúsculas, 10 dígitos y 30 signos de puntuación. Compruebe que todos los glifos sean legibles. No se requiere ninguna entrada.

Descargar código fuente / Estado de la verificación

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();  }
}
Su primer texto en pantalla fc_hello.c

Objetivo: Muestra la secuencia básica para iniciar una ROM y dibujar texto y un número en el fondo.

Funcionamiento: Después de m_init, m_text dibuja el saludo y m_number da formato a 42. El bucle final espera fotogramas y mantiene la pantalla visible.

Controles y resultado esperado: La pantalla muestra HELLO WORLD y 042. El cero inicial corresponde al formato decimal de tres dígitos.

Su primer texto en pantalla: La pantalla muestra HELLO WORLD y 042. El cero inicial corresponde al formato decimal de tres dígitos.
Ejemplo en pantalla: La pantalla muestra HELLO WORLD y 042. El cero inicial corresponde al formato decimal de tres dígitos.

Descargar código fuente / Estado de la verificación

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();  }
}
Calcular con un valor intermedio más amplio fc_arithmetic.c

Objetivo: Muestra cómo conservar un resultado intermedio que no cabe en ocho bits.

Funcionamiento: (u16)200 + 100 produce 300. El programa divide el resultado entre 10 y luego lo convierte a u8 para mostrarlo. Observa que la conversión al tipo más amplio ocurre antes de la suma.

Controles y resultado esperado: 030 representa el resultado calculado, 30.

Calcular con un valor intermedio más amplio: 030 representa el resultado calculado, 30.
Ejemplo en pantalla: 030 representa el resultado calculado, 30.

Descargar código fuente / Estado de la verificación

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();  }
}
Bifurcaciones y bucles fc_control.c

Objetivo: Prueba for, continue, while e if/else en un cálculo sencillo.

Funcionamiento: El bucle for suma de 0 a 7 saltándose el 3 y obtiene 25. El bucle while avanza hasta 32; el if siguiente asigna 42 cuando el valor coincide.

Controles y resultado esperado: 042 es el resultado final. El programa usa una bifurcación if/else.

Bifurcaciones y bucles: el resultado final es 042. Este programa utiliza una bifurcación if/else.
Ejemplo en pantalla: 042 es el resultado final. El programa usa una bifurcación if/else.

Descargar código fuente / Estado de la verificación

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();  }
}
Funciones, arrays, estructuras y punteros fc_aggregate.c

Objetivo: Muestra cómo agrupar valores y acceder a ellos mediante funciones y punteros.

Funcionamiento: El programa copia un Point que contiene (10,20) y después escribe de 1 a 4 en un array mediante un puntero. Suma el resultado de la función, 30, a los valores del array 1, 2 y 4 y a la constante 5.

Controles y resultado esperado: La suma se muestra como 042. Siga la copia de la estructura, las escrituras en ptr[i] y el valor devuelto por la función.

Funciones, arrays, estructuras y punteros: La suma se muestra como 042. Siga la copia de la estructura, las escrituras en ptr[i] y el valor devuelto por la función.
Ejemplo en pantalla: La suma se muestra como 042. Siga la copia de la estructura, las escrituras en ptr[i] y el valor devuelto por la función.

Descargar código fuente / Estado de la verificación

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();  }
}
Banderas de bits y copia de búfer fc_bits_memory.c

Objetivo: Agrupa banderas en un byte y copia un búfer en un área de almacenamiento independiente.

Funcionamiento: __memset borra cuatro bytes. El programa activa el bit 3, invierte el bit 1 y después copia los cuatro bytes con __memcpy.

Controles y resultado esperado: el primer byte de destino se muestra como 010; el bit 3 aporta 8 y el bit 1 aporta 2.

Banderas de bits y copia de búfer: el primer byte de destino se muestra como 010; el bit 3 aporta 8 y el bit 1 aporta 2.
Ejemplo en pantalla: el primer byte de destino se muestra como 010; el bit 3 aporta 8 y el bit 1 aporta 2.

Descargar código fuente / Estado de la verificación

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();  }
}
Contar nuevas pulsaciones de un botón fc_input.c

Objetivo: Distingue una nueva pulsación de un botón que sigue pulsado.

Funcionamiento: Cada fotograma llama a input_update. El contador solo avanza cuando input_pressed(BTN_A) es verdadero.

Controles y resultado esperado: el contador comienza en 000. Pulse A para obtener 001; mantenerlo pulsado no añade más. Suéltelo y vuelva a pulsarlo para obtener 002. El contador de un byte vuelve a 000 después de 255.

Contar nuevas pulsaciones de un botón: el contador comienza en 000. Pulse A para obtener 001; mantenerlo pulsado no añade más. Suéltelo y vuelva a pulsarlo para obtener 002. El contador de un byte vuelve a 000 después de 255.
Ejemplo en pantalla: el contador comienza en 000. Pulse A para obtener 001; mantenerlo pulsado no añade más. Suéltelo y vuelva a pulsarlo para obtener 002. El contador de un byte vuelve a 000 después de 255.

Descargar código fuente / Estado de la verificación

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); } }
}
Multiplicar valores de punto fijo Q8.8 fc_fixed.c

Objetivo: Muestra la conversión de entero a Q8.8, la multiplicación y la conversión de vuelta a entero.

Funcionamiento: fix_from_int convierte 3 y 4 a Q8.8. fix_mul los multiplica y fix_to_int convierte el producto de vuelta a entero.

Controles y resultado esperado: 012 significa 3 × 4 = 12. Es el entero convertido, no el valor interno escalado por 256.

Multiplicar valores de punto fijo Q8.8: 012 significa 3 × 4 = 12. Es el entero convertido, no el valor interno escalado por 256.
Ejemplo en pantalla: 012 significa 3 × 4 = 12. Es el entero convertido, no el valor interno escalado por 256.

Descargar código fuente / Estado de la verificación

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();  }
}
Asignar desde un pool fijo de objetos fc_entity.c

Objetivo: Crea un objeto a partir de un conjunto fijo de posiciones, sin asignación dinámica de memoria.

Funcionamiento: entity_init reinicia el pool. entity_create crea el tipo 1 en (20,30); el programa comprueba el ID de fallo 0xFF antes de establecer vx en 2.

Controles y resultado esperado: 001 es la cantidad de objetos activos, no un ID ni una coordenada. Este ejemplo no actualiza ni dibuja el objeto, por lo que no aparece ningún personaje en movimiento.

Asignar desde un pool fijo de objetos: 001 es la cantidad de objetos activos, no un ID ni una coordenada. Este ejemplo no actualiza ni dibuja el objeto, por lo que no aparece ningún personaje en movimiento.
Ejemplo en pantalla: 001 es la cantidad de objetos activos, no un ID ni una coordenada. Este ejemplo no actualiza ni dibuja el objeto, por lo que no aparece ningún personaje en movimiento.

Descargar código fuente / Estado de la verificación

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();  }
}
Registrar un valor de diagnóstico en RAM fc_debug.c

Objetivo: Conserva un valor del juego con nombre en un búfer de diagnóstico, separado de la visualización en pantalla.

Funcionamiento: El programa inicializa la depuración, establece la etiqueta de fotograma en 1 y registra el valor 42 con el nombre HP.

Controles y resultado esperado: 001 es la cantidad de entradas de traza. HP=42 se guarda en el búfer de diagnóstico; la pantalla por sí sola no muestra ni verifica ese contenido.

Registrar un valor de diagnóstico en RAM: 001 es la cantidad de entradas de traza. HP=42 se guarda en el búfer de diagnóstico; la pantalla por sí sola no muestra ni verifica ese contenido.
Ejemplo en pantalla: 001 es la cantidad de entradas de traza. HP=42 se guarda en el búfer de diagnóstico; la pantalla por sí sola no muestra ni verifica ese contenido.

Descargar código fuente / Estado de la verificación

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();  }
}
Guardar un historial de posiciones fc_chain.c

Objetivo: Muestra cómo guardar un historial de posiciones en un búfer circular.

Funcionamiento: chain_init asocia un array con espacio para cuatro puntos. Después, chain_push_head registra (20,30).

Controles y resultado esperado: 042 es un marcador fijo de finalización, no una coordenada ni una cantidad. Inspecciona trail y points para comprobar el historial guardado. Este programa no dibuja un cuerpo que siga al protagonista.

Guardar un historial de posiciones: 042 es un marcador fijo de finalización, no una coordenada ni una cantidad. Inspecciona trail y points para comprobar el historial guardado. Este programa no dibuja un cuerpo que siga al protagonista.
Ejemplo en pantalla: 042 es un marcador fijo de finalización, no una coordenada ni una cantidad. Inspecciona trail y points para comprobar el historial guardado. Este programa no dibuja un cuerpo que siga al protagonista.

Descargar código fuente / Estado de la verificación

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();  }
}
Leer el contador de fotogramas de la biblioteca fc_system.c

Objetivo: Muestra cómo las esperas contadas en fotogramas afectan al contador de fotogramas de la biblioteca.

Funcionamiento: Después de system_init, el programa llama dos veces a system_wait_vblank y muestra system_get_frame8.

Controles y resultado esperado: El resultado es 002. El bucle de espera utiliza la función auxiliar m_wait del manual, por lo que el valor mostrado no sigue aumentando.

Leer el contador de fotogramas de la biblioteca: El resultado es 002. El bucle de espera utiliza la función auxiliar m_wait del manual, por lo que el valor mostrado no sigue aumentando.
Ejemplo en pantalla: El resultado es 002. El bucle de espera utiliza la función auxiliar m_wait del manual, por lo que el valor mostrado no sigue aumentando.

Descargar código fuente / Estado de la verificación

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();  }
}
Seleccionar el ID de la escena actual fc_scene.c

Objetivo: Muestra cómo seleccionar un ID para distinguir escenas, como la pantalla de título y la partida.

Funcionamiento: El programa registra dos entradas SceneDef inicializadas a cero y selecciona el ID 1 mediante scene_change.

Controles y resultado esperado: 001 es el ID actual devuelto por scene_get_current. El ejemplo no registra callbacks de escena ni dibujo de transiciones.

Seleccionar el ID de la escena actual: 001 es el ID actual devuelto por scene_get_current. El ejemplo no registra callbacks de escena ni dibujo de transiciones.
Ejemplo en pantalla: 001 es el ID actual devuelto por scene_get_current. El ejemplo no registra callbacks de escena ni dibujo de transiciones.

Descargar código fuente / Estado de la verificación

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();  }
}
Acumular movimiento fraccionario Q5.3 fc_subpixel.c

Objetivo: Conserva el movimiento subpíxel como resto y suma la parte de píxeles enteros a la posición.

Funcionamiento: Partiendo de X=40, el bucle suma 2/8 de píxel ocho veces. Un desplazamiento a la derecha extrae los píxeles enteros; una máscara conserva el resto.

Controles y resultado esperado: el movimiento total es de dos píxeles, así que X=42 se muestra como 042. Este ejemplo de integración manual maneja movimiento positivo; el negativo necesita una lógica equivalente de préstamo.

Acumular movimiento fraccionario Q5.3: el movimiento total es de dos píxeles, así que X=42 se muestra como 042. Este ejemplo de integración manual maneja movimiento positivo; el negativo necesita una lógica equivalente de préstamo.
Ejemplo en pantalla: el movimiento total es de dos píxeles, así que X=42 se muestra como 042. Este ejemplo de integración manual maneja movimiento positivo; el negativo necesita una lógica equivalente de préstamo.

Descargar código fuente / Estado de la verificación

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();  }
}
Desplazar el fondo de NES fc_scroll.c

Objetivo: Mueve el fondo actualizando la posición de desplazamiento de PPU.

Funcionamiento: Partiendo de __scroll_set(0,0), el programa incrementa phase una vez por fotograma y lo utiliza como posición horizontal.

Controles y resultado esperado: La palabra SCROLL cambia de posición horizontal. phase vuelve de 255 a 0. No es un ejemplo de un mapa ancho que abarque varias tablas de nombres.

Desplazar el fondo de NES: La palabra SCROLL cambia de posición horizontal. phase vuelve de 255 a 0. No es un ejemplo de un mapa ancho que abarque varias tablas de nombres.
Ejemplo en pantalla: La palabra SCROLL cambia de posición horizontal. phase vuelve de 255 a 0. No es un ejemplo de un mapa ancho que abarque varias tablas de nombres.

Descargar código fuente / Estado de la verificación

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); }
}
Aplicar una actualización de tile mediante NMI fc_vram_queue.c

Objetivo: Envía una actualización de PPU desde el programa principal para que la aplique el procesamiento de NMI.

Funcionamiento: __vramq_put encola el tile 52 en la dirección de tabla de nombres 0x2103 y __vramq_commit lo envía. Esa dirección corresponde a la coordenada de tile (3,8).

Controles y resultado esperado: Aparece un único 4 en la coordenada del fondo (3,8). El tile 52 es el dígito 4 en la fuente incluida.

Aplicar una actualización de tile mediante NMI: aparece un único 4 en la coordenada del fondo (3,8). El tile 52 es el dígito 4 en la fuente incluida.
Ejemplo en pantalla: Aparece un único 4 en la coordenada del fondo (3,8). El tile 52 es el dígito 4 en la fuente incluida.

Descargar código fuente / Estado de la verificación

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();  }
}
Mostrar un sprite de OAM de NES fc_sprite.c

Objetivo: Prepara los colores de los sprites y los datos de OAM para dibujar una imagen de forma independiente del fondo.

Funcionamiento: El programa carga la paleta de sprites, configura la entrada 0 con posición (70,80), tile 65 y atributos 0, activa el renderizado y repite el DMA de OAM.

Controles y resultado esperado: Aparece una letra A inmóvil como sprite. El ejemplo muestra la configuración y la transferencia OAM, no una animación.

Mostrar un sprite de OAM de NES: aparece una letra A inmóvil como sprite. El ejemplo muestra la configuración y la transferencia de OAM, no una animación.
Ejemplo en pantalla: Aparece una letra A inmóvil como sprite. El ejemplo muestra la configuración y la transferencia OAM, no una animación.

Descargar código fuente / Estado de la verificación

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(); }
}
Reproducir un tono de pulso de APU fc_sound.c

Objetivo: Configura los valores de control, temporizador y longitud del canal de pulso integrado de NES.

Funcionamiento: Después de nes_apu_init, el programa llama a nes_sfx_square1(0xBF,400,20). El control 0xBF selecciona volumen constante y detiene el contador de longitud, por lo que el tono se mantiene. 20 es un índice de la tabla de longitudes, no 20 fotogramas.

Controles y resultado esperado: Suena un tono de pulso sostenido mientras se muestra 042. Llama a nes_apu_silence_all para detenerlo. El marcador en pantalla no verifica por sí solo la salida de sonido.

Reproducir un tono de pulso de APU: Suena un tono de pulso sostenido mientras se muestra 042. Llama a nes_apu_silence_all para detenerlo. El marcador en pantalla no verifica por sí solo la salida de sonido.
Ejemplo en pantalla: Suena un tono de pulso sostenido mientras se muestra 042. Llama a nes_apu_silence_all para detenerlo. El marcador en pantalla no verifica por sí solo la salida de sonido.

Descargar código fuente / Estado de la verificación

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

Diccionario de funciones intrínsecas del compilador

161 entradas. Abre una entrada para consultar su sintaxis, argumentos, notas del código fuente y ejemplo de uso. Los fragmentos de llamada requieren inicialización previa; no son ROM independientes. Los ejemplos de paso de argumentos muestran cómo llegar a la API con los valores preparados, pero no demuestran su funcionamiento en el dispositivo.

audio_vblank.h — Reproducción musical mediante la interrupción VBlank

La música de cuatro canales avanza en la NMI. Inicializa, asocia una canción y llena la cola inicialmente; después habilita la NMI para reproducir y sigue rellenando desde el programa principal. La NMI lee una cola RAM, por lo que una pequeña cantidad de RAM basta para un flujo largo si se repone a tiempo. Cada canal dispone de órdenes HOLD/STOP independientes.

Cada registro contiene cinco bytes: delay, CH1, CH2, CH3, CH4. Un retardo de 1 a 255 indica cuántos ticks de NMI dura el nuevo estado. Cero finaliza la reproducción e ignora los otros cuatro bytes. Las notas de 0 a 71 corresponden a C2–B7 (24=C4). Los valores de ruido de 0 a 15 seleccionan un periodo en modo largo y los de 16 a 31, el modo corto. En cualquier canal, 255=NES_AUDIO_HOLD conserva su estado y 254=NES_AUDIO_STOP lo silencia.

Este controlador de cuatro canales está ajustado para NTSC: CH1=pulso 1, CH2=pulso 2, CH3=triángulo y CH4=ruido, con índices del 0 al 3. No reproduce DMC ni audio de expansión. No lo ejecutes junto con código que escriba directamente en la APU, como los efectos de audio.c. No incluye una tabla de tonos PAL ni corrección del tempo para PAL.

Si el retardo termina sin otro registro BGM, se detienen las notas BGM y la reproducción se reanuda en una NMI después de recibir datos. Un SFX independiente sigue sonando. Se cuentan las entradas en falta de datos, no cada fotograma con la cola vacía. Insertar solo valores HOLD no restaura las notas BGM anteriores. Normalmente debes rellenar la cola en cada fotograma y dejar suficiente tiempo de reproducción preparado antes de una tarea larga.

fds.h — Operaciones de disco FDS

__fds_available

Indica si el destino de compilación es FDS, por ejemplo para mostrar información específica del destino desde código de juego compartido.

Sintaxis

c
u8 __fds_available(void);

Valor devuelto

1 en una compilación FDS y 0 en otro caso. No detecta periféricos en tiempo de ejecución.

Condiciones y límites

Esta consulta es válida para destinos de cartucho como NROM. Poner una API exclusiva de FDS dentro de if (__fds_available()) no la exime de las comprobaciones del destino; organiza el código específico de cada destino.

La captura usa la carga directa de los archivos de arranque del disco en RAM de KUROSAKI. El ejemplo no llama a rutinas BIOS ni verifica transferencias de archivos, cambios de disco o arranque en hardware. El firmware de prueba vacío no sustituye al BIOS. El ejemplo usa NMI para actualizar la pantalla y deshabilita las IRQ enmascarables, que estas consultas no necesitan.

El ejemplo de compilación genera un .fds con distribución nativa, sin archivos de arranque adicionales para la pantalla de aprobación. Esta configuración sirve para comprobar el arranque directo de KUROSAKI; el arranque de un disco en hardware requiere validación aparte.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

La compilación FDS muestra 001 y la NROM, 000. El mismo código C devuelve un valor distinto según el destino de compilación, independientemente de si hay un disco insertado.

__fds_available: AVAILABLE-FDS-DEFAULT — La compilación FDS muestra 001 y la NROM, 000. El mismo código C devuelve un valor distinto según el destino de compilación, independientemente de si hay un disco insertado.
AVAILABLE-FDS-DEFAULT — La compilación FDS muestra 001 y la NROM, 000. El mismo código C devuelve un valor distinto según el destino de compilación, independientemente de si hay un disco insertado.
__fds_available: AVAILABLE-NROM-DEFAULT — La compilación FDS muestra 001 y la NROM, 000. El mismo código C devuelve un valor distinto según el destino de compilación, independientemente de si hay un disco insertado.
AVAILABLE-NROM-DEFAULT — La compilación FDS muestra 001 y la NROM, 000. El mismo código C devuelve un valor distinto según el destino de compilación, independientemente de si hay un disco insertado.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds.h:7

Notas de la declaración (original)
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

Comprueba inmediatamente la presencia de disco para que un aviso de inserción pueda consultar una vez por fotograma sin bloquear.

Sintaxis

c
u8 __fds_disk_ready(void);

Valor devuelto

Devuelve 1 si el bit 0 de $4032 está a cero y 0 si está a uno.

Condiciones y límites

Compila con --mapper=fds. Para un mapper de cartucho, llamar a una función exclusiva de FDS distinta de __fds_available se rechaza con KQFC2420.

La comprobación usa el bit 0 de $4032, que indica la presencia de disco. No comprueba la disponibilidad de la unidad del bit 1, la existencia de archivos ni la finalización de una carga. Aquí «ready» no significa que se pueda iniciar una transferencia.

KUROSAKI usa un modelo parcial del controlador FDS. Este ejemplo observa presencia 1, máscara de protección 0 y estado 0; la captura no demuestra el comportamiento ante expulsión, medios protegidos o errores CRC.

La captura usa la carga directa de los archivos de arranque del disco en RAM de KUROSAKI. El ejemplo no llama a rutinas BIOS ni verifica transferencias de archivos, cambios de disco o arranque en hardware. El firmware de prueba vacío no sustituye al BIOS. El ejemplo usa NMI para actualizar la pantalla y deshabilita las IRQ enmascarables, que estas consultas no necesitan.

El ejemplo de compilación genera un .fds con distribución nativa, sin archivos de arranque adicionales para la pantalla de aprobación. Esta configuración sirve para comprobar el arranque directo de KUROSAKI; el arranque de un disco en hardware requiere validación aparte.

NESdev: registros de hardware de FDS

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Bajo PRESENT / PROTECT / STATUS se muestra 1 / 0 / 0 y, bajo COMPLETED PRESENCE WAITS, 2. Ambas esperas de presencia terminaron; no es un indicador de éxito de transferencia de archivos. Debajo aparece NO FILE TRANSFER TESTED.

__fds_disk_ready: STATUS-FDS-DEFAULT — Bajo PRESENT / PROTECT / STATUS se muestra 1 / 0 / 0 y, bajo COMPLETED PRESENCE WAITS, 2. Ambas esperas de presencia terminaron; no es un indicador de éxito de transferencia de archivos. Debajo aparece NO FILE TRANSFER TESTED.
STATUS-FDS-DEFAULT — Bajo PRESENT / PROTECT / STATUS se muestra 1 / 0 / 0 y, bajo COMPLETED PRESENCE WAITS, 2. Ambas esperas de presencia terminaron; no es un indicador de éxito de transferencia de archivos. Debajo aparece NO FILE TRANSFER TESTED.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds.h:9

Notas de la declaración (original)
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

Lee el registro de estado del hardware FDS en $4030. No es un error almacenado de la última operación de disco.

Sintaxis

c
u8 __fds_error(void);

Valor devuelto

Devuelve el valor completo de ocho bits de $4030. Conserva el valor devuelto por __fds_load_file() o __fds_save_file() para gestionar los códigos de error BIOS.

Condiciones y límites

Compila con --mapper=fds. Para un mapper de cartucho, llamar a una función exclusiva de FDS distinta de __fds_available se rechaza con KQFC2420.

KUROSAKI usa un modelo parcial del controlador FDS. Este ejemplo observa presencia 1, máscara de protección 0 y estado 0; la captura no demuestra el comportamiento ante expulsión, medios protegidos o errores CRC.

Leer el estado también reconoce las peticiones de interrupción. No consultes indiscriminadamente si otro manejador depende del mismo estado. KUROSAKI borra su bandera de IRQ FDS pendiente al leerlo.

La captura usa la carga directa de los archivos de arranque del disco en RAM de KUROSAKI. El ejemplo no llama a rutinas BIOS ni verifica transferencias de archivos, cambios de disco o arranque en hardware. El firmware de prueba vacío no sustituye al BIOS. El ejemplo usa NMI para actualizar la pantalla y deshabilita las IRQ enmascarables, que estas consultas no necesitan.

El ejemplo de compilación genera un .fds con distribución nativa, sin archivos de arranque adicionales para la pantalla de aprobación. Esta configuración sirve para comprobar el arranque directo de KUROSAKI; el arranque de un disco en hardware requiere validación aparte.

NESdev: registros de hardware de FDS

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Bajo PRESENT / PROTECT / STATUS se muestra 1 / 0 / 0 y, bajo COMPLETED PRESENCE WAITS, 2. Ambas esperas de presencia terminaron; no es un indicador de éxito de transferencia de archivos. Debajo aparece NO FILE TRANSFER TESTED.

__fds_error: STATUS-FDS-DEFAULT — Bajo PRESENT / PROTECT / STATUS se muestra 1 / 0 / 0 y, bajo COMPLETED PRESENCE WAITS, 2. Ambas esperas de presencia terminaron; no es un indicador de éxito de transferencia de archivos. Debajo aparece NO FILE TRANSFER TESTED.
STATUS-FDS-DEFAULT — Bajo PRESENT / PROTECT / STATUS se muestra 1 / 0 / 0 y, bajo COMPLETED PRESENCE WAITS, 2. Ambas esperas de presencia terminaron; no es un indicador de éxito de transferencia de archivos. Debajo aparece NO FILE TRANSFER TESTED.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds.h:13

Notas de la declaración (original)
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

Devuelve el bit de protección contra escritura enmascarado de $4032. Esta función no identifica la cara A o B del disco.

Sintaxis

c
u8 __fds_side(void);

Valor devuelto

Devuelve $4032 & 0x04: 0 o 4, no un booleano normalizado. El bit 2 del hardware indica protección contra escritura o disco expulsado.

Condiciones y límites

Compila con --mapper=fds. Para un mapper de cartucho, llamar a una función exclusiva de FDS distinta de __fds_available se rechaza con KQFC2420.

KUROSAKI usa un modelo parcial del controlador FDS. Este ejemplo observa presencia 1, máscara de protección 0 y estado 0; la captura no demuestra el comportamiento ante expulsión, medios protegidos o errores CRC.

Para comprobar la protección, usa (__fds_side() != 0). También necesitas la presencia de disco y el valor devuelto por la operación BIOS para determinar si se guarda correctamente.

La captura usa la carga directa de los archivos de arranque del disco en RAM de KUROSAKI. El ejemplo no llama a rutinas BIOS ni verifica transferencias de archivos, cambios de disco o arranque en hardware. El firmware de prueba vacío no sustituye al BIOS. El ejemplo usa NMI para actualizar la pantalla y deshabilita las IRQ enmascarables, que estas consultas no necesitan.

El ejemplo de compilación genera un .fds con distribución nativa, sin archivos de arranque adicionales para la pantalla de aprobación. Esta configuración sirve para comprobar el arranque directo de KUROSAKI; el arranque de un disco en hardware requiere validación aparte.

NESdev: registros de hardware de FDS

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Bajo PRESENT / PROTECT / STATUS se muestra 1 / 0 / 0 y, bajo COMPLETED PRESENCE WAITS, 2. Ambas esperas de presencia terminaron; no es un indicador de éxito de transferencia de archivos. Debajo aparece NO FILE TRANSFER TESTED.

__fds_side: STATUS-FDS-DEFAULT — Bajo PRESENT / PROTECT / STATUS se muestra 1 / 0 / 0 y, bajo COMPLETED PRESENCE WAITS, 2. Ambas esperas de presencia terminaron; no es un indicador de éxito de transferencia de archivos. Debajo aparece NO FILE TRANSFER TESTED.
STATUS-FDS-DEFAULT — Bajo PRESENT / PROTECT / STATUS se muestra 1 / 0 / 0 y, bajo COMPLETED PRESENCE WAITS, 2. Ambas esperas de presencia terminaron; no es un indicador de éxito de transferencia de archivos. Debajo aparece NO FILE TRANSFER TESTED.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds.h:11

Notas de la declaración (original)
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

Expresa una espera de inserción de disco. Llama al mismo bucle de sondeo de presencia que __fds_wait_ready().

Sintaxis

c
void __fds_wait_insert(void);

Valor devuelto

No devuelve ningún valor.

Condiciones y límites

Compila con --mapper=fds. Para un mapper de cartucho, llamar a una función exclusiva de FDS distinta de __fds_available se rechaza con KQFC2420.

La comprobación usa el bit 0 de $4032, que indica la presencia de disco. No comprueba la disponibilidad de la unidad del bit 1, la existencia de archivos ni la finalización de una carga. Aquí «ready» no significa que se pueda iniciar una transferencia.

No hay tiempo de espera máximo. Sin disco, la llamada queda bloqueada. Consulta __fds_disk_ready() una vez por fotograma si el juego debe seguir actualizando la pantalla o aceptando entradas. El ejemplo comprueba la presencia antes de ambas esperas. Un disco ya insertado satisface la espera sin tener que retirarlo y volverlo a insertar.

KUROSAKI usa un modelo parcial del controlador FDS. Este ejemplo observa presencia 1, máscara de protección 0 y estado 0; la captura no demuestra el comportamiento ante expulsión, medios protegidos o errores CRC.

La captura usa la carga directa de los archivos de arranque del disco en RAM de KUROSAKI. El ejemplo no llama a rutinas BIOS ni verifica transferencias de archivos, cambios de disco o arranque en hardware. El firmware de prueba vacío no sustituye al BIOS. El ejemplo usa NMI para actualizar la pantalla y deshabilita las IRQ enmascarables, que estas consultas no necesitan.

El ejemplo de compilación genera un .fds con distribución nativa, sin archivos de arranque adicionales para la pantalla de aprobación. Esta configuración sirve para comprobar el arranque directo de KUROSAKI; el arranque de un disco en hardware requiere validación aparte.

NESdev: registros de hardware de FDS

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Bajo PRESENT / PROTECT / STATUS se muestra 1 / 0 / 0 y, bajo COMPLETED PRESENCE WAITS, 2. Ambas esperas de presencia terminaron; no es un indicador de éxito de transferencia de archivos. Debajo aparece NO FILE TRANSFER TESTED.

__fds_wait_insert: STATUS-FDS-DEFAULT — Bajo PRESENT / PROTECT / STATUS se muestra 1 / 0 / 0 y, bajo COMPLETED PRESENCE WAITS, 2. Ambas esperas de presencia terminaron; no es un indicador de éxito de transferencia de archivos. Debajo aparece NO FILE TRANSFER TESTED.
STATUS-FDS-DEFAULT — Bajo PRESENT / PROTECT / STATUS se muestra 1 / 0 / 0 y, bajo COMPLETED PRESENCE WAITS, 2. Ambas esperas de presencia terminaron; no es un indicador de éxito de transferencia de archivos. Debajo aparece NO FILE TRANSFER TESTED.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds.h:17

Notas de la declaración (original)
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

Espera en un bucle de sondeo de CPU hasta que haya un disco.

Sintaxis

c
void __fds_wait_ready(void);

Valor devuelto

No devuelve ningún valor.

Condiciones y límites

Compila con --mapper=fds. Para un mapper de cartucho, llamar a una función exclusiva de FDS distinta de __fds_available se rechaza con KQFC2420.

La comprobación usa el bit 0 de $4032, que indica la presencia de disco. No comprueba la disponibilidad de la unidad del bit 1, la existencia de archivos ni la finalización de una carga. Aquí «ready» no significa que se pueda iniciar una transferencia.

No hay tiempo de espera máximo. Sin disco, la llamada queda bloqueada. Consulta __fds_disk_ready() una vez por fotograma si el juego debe seguir actualizando la pantalla o aceptando entradas. El ejemplo comprueba la presencia antes de ambas esperas. Un disco ya insertado satisface la espera sin tener que retirarlo y volverlo a insertar.

KUROSAKI usa un modelo parcial del controlador FDS. Este ejemplo observa presencia 1, máscara de protección 0 y estado 0; la captura no demuestra el comportamiento ante expulsión, medios protegidos o errores CRC.

La captura usa la carga directa de los archivos de arranque del disco en RAM de KUROSAKI. El ejemplo no llama a rutinas BIOS ni verifica transferencias de archivos, cambios de disco o arranque en hardware. El firmware de prueba vacío no sustituye al BIOS. El ejemplo usa NMI para actualizar la pantalla y deshabilita las IRQ enmascarables, que estas consultas no necesitan.

El ejemplo de compilación genera un .fds con distribución nativa, sin archivos de arranque adicionales para la pantalla de aprobación. Esta configuración sirve para comprobar el arranque directo de KUROSAKI; el arranque de un disco en hardware requiere validación aparte.

NESdev: registros de hardware de FDS

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Bajo PRESENT / PROTECT / STATUS se muestra 1 / 0 / 0 y, bajo COMPLETED PRESENCE WAITS, 2. Ambas esperas de presencia terminaron; no es un indicador de éxito de transferencia de archivos. Debajo aparece NO FILE TRANSFER TESTED.

__fds_wait_ready: STATUS-FDS-DEFAULT — Bajo PRESENT / PROTECT / STATUS se muestra 1 / 0 / 0 y, bajo COMPLETED PRESENCE WAITS, 2. Ambas esperas de presencia terminaron; no es un indicador de éxito de transferencia de archivos. Debajo aparece NO FILE TRANSFER TESTED.
STATUS-FDS-DEFAULT — Bajo PRESENT / PROTECT / STATUS se muestra 1 / 0 / 0 y, bajo COMPLETED PRESENCE WAITS, 2. Ambas esperas de presencia terminaron; no es un indicador de éxito de transferencia de archivos. Debajo aparece NO FILE TRANSFER TESTED.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds.h:15

Notas de la declaración (original)
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 — Carga de archivos FDS

__fds_file_exists

Comprueba si los metadatos de archivos compilados contienen el ID solicitado. Un registro de tamaño cero sigue contando como presente.

Sintaxis

c
u8 __fds_file_exists(u8 id);

Parámetros

id

ID de archivo que se busca. Los metadatos usan ID 0..254; 255 se reserva como terminador. Un ID es distinto de un número de archivo, un nombre de archivo o una dirección de destino.

Valor devuelto

1 si la tabla compilada contiene el ID y 0 en otro caso. No garantiza que el disco insertado contenga ese archivo.

Condiciones y límites

Compila con --mapper=fds. Para un mapper de cartucho, llamar a una función exclusiva de FDS distinta de __fds_available se rechaza con KQFC2420.

Busca en la tabla compilada desde --fds-meta=... o --fds-manifest=..., junto con los registros de overlays generados. No lee el directorio del disco. Un archivo sin registro en la tabla no se encuentra aunque esté incluido en el disco. El total de registros manuales y generados está limitado a 42.

El tamaño es el valor declarado en los metadatos. No tiene por qué coincidir con la longitud deducida del archivo de origen, los datos útiles de un overlay recortado o el contenido de un disco sustituido. Declara tamaños precisos si usas estas consultas para limitar el acceso a datos.

Coloca fds_metadata.json y sus dos archivos .bin junto al código C. El ID 16 contiene una secuencia original de 513 bytes: 0..255 dos veces y luego cero. El ID 17 tiene contenido vacío. Ambos son archivos PRG no destinados al arranque (tipo 0), y esta lección no los carga. Las rutas relativas de origen se resuelven desde la carpeta del manifiesto.

La captura usa la carga directa de los archivos de arranque del disco en RAM de KUROSAKI. El ejemplo no llama a rutinas BIOS ni verifica transferencias de archivos, cambios de disco o arranque en hardware. El firmware de prueba vacío no sustituye al BIOS. El ejemplo usa NMI para actualizar la pantalla y deshabilita las IRQ enmascarables, que estas consultas no necesitan.

El ejemplo de compilación genera un .fds con distribución nativa, sin archivos de arranque adicionales para la pantalla de aprobación. Esta configuración sirve para comprobar el arranque directo de KUROSAKI; el arranque de un disco en hardware requiere validación aparte.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

La tabla muestra el ID 16 presente con 513 bytes, el ID 17 presente con cero bytes y el ID 18 ausente con cero bytes. TABLE VALUES MATCH: PASS compara los valores obtenidos con esa combinación. Un tamaño cero por sí solo no distingue un registro vacío de un ID inexistente.

__fds_file_exists: METADATA-FDS-DEFAULT — La tabla muestra el ID 16 presente con 513 bytes, el ID 17 presente con cero bytes y el ID 18 ausente con cero bytes. TABLE VALUES MATCH: PASS compara los valores obtenidos con esa combinación. Un tamaño cero por sí solo no distingue un registro vacío de un ID inexistente.
METADATA-FDS-DEFAULT — La tabla muestra el ID 16 presente con 513 bytes, el ID 17 presente con cero bytes y el ID 18 ausente con cero bytes. TABLE VALUES MATCH: PASS compara los valores obtenidos con esa combinación. Un tamaño cero por sí solo no distingue un registro vacío de un ID inexistente.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_file.h:18

Notas de la declaración (original)
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

Devuelve el tamaño en bytes declarado para un ID en los metadatos compilados, por ejemplo al consultar el tamaño de carga previsto de un recurso.

Sintaxis

c
u16 __fds_file_size(u8 id);

Parámetros

id

ID de archivo que se busca. Los metadatos usan ID 0..254; 255 se reserva como terminador. Un ID es distinto de un número de archivo, un nombre de archivo o una dirección de destino.

Valor devuelto

Tamaño declarado de 16 bits sin signo, de 0 a 65535. Los ID inexistentes también devuelven cero; usa __fds_file_exists() cuando necesites distinguirlos.

Condiciones y límites

Compila con --mapper=fds. Para un mapper de cartucho, llamar a una función exclusiva de FDS distinta de __fds_available se rechaza con KQFC2420.

Busca en la tabla compilada desde --fds-meta=... o --fds-manifest=..., junto con los registros de overlays generados. No lee el directorio del disco. Un archivo sin registro en la tabla no se encuentra aunque esté incluido en el disco. El total de registros manuales y generados está limitado a 42.

El tamaño es el valor declarado en los metadatos. No tiene por qué coincidir con la longitud deducida del archivo de origen, los datos útiles de un overlay recortado o el contenido de un disco sustituido. Declara tamaños precisos si usas estas consultas para limitar el acceso a datos.

Coloca fds_metadata.json y sus dos archivos .bin junto al código C. El ID 16 contiene una secuencia original de 513 bytes: 0..255 dos veces y luego cero. El ID 17 tiene contenido vacío. Ambos son archivos PRG no destinados al arranque (tipo 0), y esta lección no los carga. Las rutas relativas de origen se resuelven desde la carpeta del manifiesto.

La captura usa la carga directa de los archivos de arranque del disco en RAM de KUROSAKI. El ejemplo no llama a rutinas BIOS ni verifica transferencias de archivos, cambios de disco o arranque en hardware. El firmware de prueba vacío no sustituye al BIOS. El ejemplo usa NMI para actualizar la pantalla y deshabilita las IRQ enmascarables, que estas consultas no necesitan.

El ejemplo de compilación genera un .fds con distribución nativa, sin archivos de arranque adicionales para la pantalla de aprobación. Esta configuración sirve para comprobar el arranque directo de KUROSAKI; el arranque de un disco en hardware requiere validación aparte.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

La tabla muestra el ID 16 presente con 513 bytes, el ID 17 presente con cero bytes y el ID 18 ausente con cero bytes. TABLE VALUES MATCH: PASS compara los valores obtenidos con esa combinación. Un tamaño cero por sí solo no distingue un registro vacío de un ID inexistente.

__fds_file_size: METADATA-FDS-DEFAULT — La tabla muestra el ID 16 presente con 513 bytes, el ID 17 presente con cero bytes y el ID 18 ausente con cero bytes. TABLE VALUES MATCH: PASS compara los valores obtenidos con esa combinación. Un tamaño cero por sí solo no distingue un registro vacío de un ID inexistente.
METADATA-FDS-DEFAULT — La tabla muestra el ID 16 presente con 513 bytes, el ID 17 presente con cero bytes y el ID 18 ausente con cero bytes. TABLE VALUES MATCH: PASS compara los valores obtenidos con esa combinación. Un tamaño cero por sí solo no distingue un registro vacío de un ID inexistente.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_file.h:20

Notas de la declaración (original)
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

Lee un archivo mediante el BIOS y copia sus datos PRG a un búfer CPU. Úsalo para cargar datos guardados o recursos de nivel en la memoria reservada por el juego.

Sintaxis

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

Parámetros

id

ID de un archivo que no sea de arranque, incluido en el disco generado, de 0 a 254; el ejemplo usa 73. Es distinto de la posición física del archivo. Se rechazan el ID 255, los ID inexistentes y los archivos de arranque.

dst

Destino de un archivo PRG, con espacio para toda su longitud empaquetada. Pasa cero para dejar los datos en el destino definido por su cabecera sin copiarlos. Para archivos PPU de CHR o tabla de nombres solo se acepta cero; el BIOS transfiere al destino PPU nativo.

Valor devuelto

Cero si tiene éxito. Los errores BIOS distintos de cero se devuelven sin cambios. Si el BIOS declara éxito pero no ha cargado exactamente un archivo, devuelve 64 ($40). Los ID, búferes o combinaciones de tipo de archivo y destino no válidos devuelven 255 ($FF).

Condiciones y límites

Úsalo con --mapper=fds --fds-layout=fds32. El manifiesto --fds-meta define los ID de archivo, las direcciones de carga, los tipos, el estado de arranque y el orden. Las direcciones y tamaños de transferencia proceden de la imagen de disco generada al compilar; esta API no examina un disco arbitrario que se inserte.

El BIOS carga primero en la dirección almacenada en la cabecera del disco y luego esta función copia a dst. Elige el sentido de copia para preservar datos solapados, pero también debes reservar la zona intermedia. Si la transferencia falla, no copia a dst. Tras una llamada al BIOS, el registro del banco residente se marca como 255 (desconocido).

Cada búfer CPU debe caber por completo en $0200–$07FF o en $6000–$DFFF. Se rechazan los rangos que desbordan y vuelven al principio del espacio de direcciones y los destinos en registros de E/S. Quien llama debe evitar además que se solapen con código, OAM, otras variables o temporales del compilador. El ejemplo usa el banco 0 para código común, $8000 como zona intermedia y búferes de cuatro bytes en $8500 y $8600.

Son llamadas síncronas al BIOS. Llámalas desde código común para que la transferencia no sobrescriba el código en ejecución ni su dirección de retorno; no las llames desde un manejador de interrupciones. Durante la E/S de disco, cumple los requisitos del BIOS: detén el renderizado y la NMI, usa un incremento de dirección PPU de uno y deshabilita las fuentes IRQ ajenas al disco. No se garantiza que el juego o la música continúen durante la transferencia.

La captura utiliza un programa de prueba original de la convención de llamada al BIOS para comprobar el código CPU compilado, el contenido de los búferes, el guardado y la posterior lectura y cada píxel mostrado. No verifica el BIOS real, las escrituras en disco físico, el CRC ni la recuperación tras un corte de corriente. La ejecución FDS normal en KUROSAKI por sí sola no demuestra el mismo comportamiento de guardado.

Usa el ejemplo junto con fds_file_io_manifest.json y fds_file_io_payload.bin de la misma carpeta. El comprobador siguiente genera un programa de prueba temporal a partir de código original y lo elimina después. No se incluye ningún BIOS comercial y este programa de prueba no es un BIOS para juegos normales.

Convención de llamada del BIOS de FDS

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

LOAD, SAVE, RELOAD STATUS y BYTE CHECK ERRORS muestran 000. INITIAL FIRST/LAST muestra 011 y 044, los extremos de los cuatro bytes cargados en $8500. El ejemplo los cambia a 7, 14, 21 y 28, los guarda y los vuelve a cargar en $8600, por lo que SAVED FIRST / LAST muestra 007 y 028. STAGING 8000 / SIZE 4 identifica la dirección de carga definida en el disco y la longitud del archivo. La prueba independiente también comprueba que el ID 73 se guarda en el cuarto archivo físico, posición 3 contando desde cero.

__fds_load_file: Configuración predeterminada — LOAD, SAVE, RELOAD STATUS y BYTE CHECK ERRORS muestran 000. INITIAL FIRST/LAST muestra 011 y 044, los extremos de los cuatro bytes cargados en $8500. El ejemplo los cambia a 7, 14, 21 y 28, los guarda y los vuelve a cargar en $8600, por lo que SAVED FIRST / LAST muestra 007 y 028. STAGING 8000 / SIZE 4 identifica la dirección de carga definida en el disco y la longitud del archivo. La prueba independiente también comprueba que el ID 73 se guarda en el cuarto archivo físico, posición 3 contando desde cero.
Configuración predeterminada — LOAD, SAVE, RELOAD STATUS y BYTE CHECK ERRORS muestran 000. INITIAL FIRST/LAST muestra 011 y 044, los extremos de los cuatro bytes cargados en $8500. El ejemplo los cambia a 7, 14, 21 y 28, los guarda y los vuelve a cargar en $8600, por lo que SAVED FIRST / LAST muestra 007 y 028. STAGING 8000 / SIZE 4 identifica la dirección de carga definida en el disco y la longitud del archivo. La prueba independiente también comprueba que el ID 73 se guarda en el cuarto archivo físico, posición 3 contando desde cero.

Programa de ejemplo completo

fds_file_io_manifest.json

fds_file_io_payload.bin

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_file.h:11

Notas de la declaración (original)
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

Sobrescribe con los bytes de un búfer CPU una posición de guardado PRG de tamaño fijo reservada al final de una cara del disco. El archivo conserva su ID, nombre y dirección de carga futura.

Sintaxis

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

Parámetros

id

ID de un archivo que no sea de arranque, incluido en el disco generado, de 0 a 254; el ejemplo usa 73. Es distinto de la posición física del archivo. Se rechazan el ID 255, los ID inexistentes y los archivos de arranque.

src

Inicio de los datos en RAM CPU, legible durante len bytes. Esta API no guarda VRAM directamente.

len

Cantidad de bytes que se guardan; debe coincidir con la longitud del archivo empaquetado. Si el juego tiene menos datos, rellena la parte sin usar del búfer de guardado antes de llamar.

Valor devuelto

Cero si tiene éxito; en caso contrario, el error BIOS, como protección contra escritura o fallo de verificación de escritura. Devuelve 255 ($FF) sin llamar al BIOS si el ID no es válido, el archivo es de arranque o no es PRG, no es el último de su cara, la longitud no coincide o el rango CPU no es válido.

Condiciones y límites

Úsalo con --mapper=fds --fds-layout=fds32. El manifiesto --fds-meta define los ID de archivo, las direcciones de carga, los tipos, el estado de arranque y el orden. Las direcciones y tamaños de transferencia proceden de la imagen de disco generada al compilar; esta API no examina un disco arbitrario que se inserte.

WriteFile del BIOS oculta lógicamente los archivos posteriores a la posición de escritura. Por eso esta API solo acepta el último archivo PRG no destinado al arranque de cada cara. No añade archivos ni cambia el tamaño de la posición reservada. Esta debe quedar al final de la distribución definitiva, incluidos los auxiliares de arranque generados y los overlays automáticos.

El compilador distingue entre ID de archivo, número de cabecera y orden físico. El ejemplo tiene ID 73 y número 200 en el manifiesto, pero pasa la posición 3 al BIOS. Los números de disco y cara también proceden de la distribución resuelta. No lo uses con un disco cuya distribución se haya cambiado al margen del manifiesto.

Cada búfer CPU debe caber por completo en $0200–$07FF o en $6000–$DFFF. Se rechazan los rangos que desbordan y vuelven al principio del espacio de direcciones y los destinos en registros de E/S. Quien llama debe evitar además que se solapen con código, OAM, otras variables o temporales del compilador. El ejemplo usa el banco 0 para código común, $8000 como zona intermedia y búferes de cuatro bytes en $8500 y $8600.

Son llamadas síncronas al BIOS. Llámalas desde código común para que la transferencia no sobrescriba el código en ejecución ni su dirección de retorno; no las llames desde un manejador de interrupciones. Durante la E/S de disco, cumple los requisitos del BIOS: detén el renderizado y la NMI, usa un incremento de dirección PPU de uno y deshabilita las fuentes IRQ ajenas al disco. No se garantiza que el juego o la música continúen durante la transferencia.

La captura utiliza un programa de prueba original de la convención de llamada al BIOS para comprobar el código CPU compilado, el contenido de los búferes, el guardado y la posterior lectura y cada píxel mostrado. No verifica el BIOS real, las escrituras en disco físico, el CRC ni la recuperación tras un corte de corriente. La ejecución FDS normal en KUROSAKI por sí sola no demuestra el mismo comportamiento de guardado.

Usa el ejemplo junto con fds_file_io_manifest.json y fds_file_io_payload.bin de la misma carpeta. El comprobador siguiente genera un programa de prueba temporal a partir de código original y lo elimina después. No se incluye ningún BIOS comercial y este programa de prueba no es un BIOS para juegos normales.

Convención de llamada del BIOS de FDS

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

LOAD, SAVE, RELOAD STATUS y BYTE CHECK ERRORS muestran 000. INITIAL FIRST/LAST muestra 011 y 044, los extremos de los cuatro bytes cargados en $8500. El ejemplo los cambia a 7, 14, 21 y 28, los guarda y los vuelve a cargar en $8600, por lo que SAVED FIRST / LAST muestra 007 y 028. STAGING 8000 / SIZE 4 identifica la dirección de carga definida en el disco y la longitud del archivo. La prueba independiente también comprueba que el ID 73 se guarda en el cuarto archivo físico, posición 3 contando desde cero.

__fds_save_file: Configuración predeterminada — LOAD, SAVE, RELOAD STATUS y BYTE CHECK ERRORS muestran 000. INITIAL FIRST/LAST muestra 011 y 044, los extremos de los cuatro bytes cargados en $8500. El ejemplo los cambia a 7, 14, 21 y 28, los guarda y los vuelve a cargar en $8600, por lo que SAVED FIRST / LAST muestra 007 y 028. STAGING 8000 / SIZE 4 identifica la dirección de carga definida en el disco y la longitud del archivo. La prueba independiente también comprueba que el ID 73 se guarda en el cuarto archivo físico, posición 3 contando desde cero.
Configuración predeterminada — LOAD, SAVE, RELOAD STATUS y BYTE CHECK ERRORS muestran 000. INITIAL FIRST/LAST muestra 011 y 044, los extremos de los cuatro bytes cargados en $8500. El ejemplo los cambia a 7, 14, 21 y 28, los guarda y los vuelve a cargar en $8600, por lo que SAVED FIRST / LAST muestra 007 y 028. STAGING 8000 / SIZE 4 identifica la dirección de carga definida en el disco y la longitud del archivo. La prueba independiente también comprueba que el ID 73 se guarda en el cuarto archivo físico, posición 3 contando desde cero.

Programa de ejemplo completo

fds_file_io_manifest.json

fds_file_io_payload.bin

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_file.h:16

Notas de la declaración (original)
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 — Código superpuesto de FDS

__fds_current_bank

Devuelve el banco lógico registrado como residente sin leer el disco.

Sintaxis

c
u8 __fds_current_bank(void);

Valor devuelto

Registro de residencia por software de ocho bits: 1 justo después del arranque estándar o 255 si se desconoce.

Condiciones y límites

Compila con --mapper=fds. Para un mapper de cartucho, llamar a una función exclusiva de FDS distinta de __fds_available se rechaza con KQFC2420.

El arranque FDS estándar inicializa el registro de residencia a 1. __fds_load_bank() lo actualiza tras una carga correcta. Las cargas directas mediante __fds_load_file() o __fds_load_overlay() y las transferencias de banco fallidas lo ponen a 255 (desconocido). No se comprueba el contenido de la memoria; no confíes en el registro después de modificar directamente la ventana. Una rutina de reinicio propia debe gestionar la inicialización.

La captura usa la carga directa de los archivos de arranque del disco en RAM de KUROSAKI. El ejemplo no llama a rutinas BIOS ni verifica transferencias de archivos, cambios de disco o arranque en hardware. El firmware de prueba vacío no sustituye al BIOS. El ejemplo usa NMI para actualizar la pantalla y deshabilita las IRQ enmascarables, que estas consultas no necesitan.

El ejemplo de compilación genera un .fds con distribución nativa, sin archivos de arranque adicionales para la pantalla de aprobación. Esta configuración sirve para comprobar el arranque directo de KUROSAKI; el arranque de un disco en hardware requiere validación aparte.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

CURRENT BANK es 1, BANK 1 RESIDENT es 1, BANK 2 RESIDENT es 0 y OVERLAY FUNCTIONS es 2. Incluir dos funciones en el banco 2 no hace que ese banco esté residente al arrancar. En la compilación de verificación con --fds-no-overlay-table, solo el último valor cambia a cero.

__fds_current_bank: RESIDENCY-FDS-DEFAULT — CURRENT BANK es 1, BANK 1 RESIDENT es 1, BANK 2 RESIDENT es 0 y OVERLAY FUNCTIONS es 2. Incluir dos funciones en el banco 2 no hace que ese banco esté residente al arrancar. En la compilación de verificación con --fds-no-overlay-table, solo el último valor cambia a cero.
RESIDENCY-FDS-DEFAULT — CURRENT BANK es 1, BANK 1 RESIDENT es 1, BANK 2 RESIDENT es 0 y OVERLAY FUNCTIONS es 2. Incluir dos funciones en el banco 2 no hace que ese banco esté residente al arrancar. En la compilación de verificación con --fds-no-overlay-table, solo el último valor cambia a cero.
__fds_current_bank: RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK es 1, BANK 1 RESIDENT es 1, BANK 2 RESIDENT es 0 y OVERLAY FUNCTIONS es 2. Incluir dos funciones en el banco 2 no hace que ese banco esté residente al arrancar. En la compilación de verificación con --fds-no-overlay-table, solo el último valor cambia a cero.
RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK es 1, BANK 1 RESIDENT es 1, BANK 2 RESIDENT es 0 y OVERLAY FUNCTIONS es 2. Incluir dos funciones en el banco 2 no hace que ese banco esté residente al arrancar. En la compilación de verificación con --fds-no-overlay-table, solo el último valor cambia a cero.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_overlay.h:16

Notas de la declaración (original)
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

Realiza una llamada entre bancos sin parámetros con la forma específica de FDS. La carga y restauración se comportan exactamente como en __fds_overlay_farcall().

Sintaxis

c
u8 __fds_farcall(u8 bank, u16 func);

Parámetros

bank

Expresión que representa el banco. Se evalúa una vez antes de llamar, pero el banco que realmente se carga procede de la ubicación de func tras el enlazado. No llama a una dirección de un banco arbitrario elegido en tiempo de ejecución. Pasa un valor coincidente, como __bankof(func).

func

Nombre de una función declarada sin parámetros. El ejemplo pasa u8 overlay_answer(void). Se rechazan las direcciones numéricas, los punteros de función en tiempo de ejecución y las funciones que requieren argumentos. Usa una llamada normal target(a, b) si necesitas argumentos.

Valor devuelto

Resultado de ocho bits de la función de destino. Si falla la carga y se puede restaurar el banco anterior, no se llama al destino y se devuelve el byte de error de carga. Diseña la convención de resultados para poder distinguir un error de un resultado normal.

Condiciones y límites

Úsalo con --mapper=fds --fds-layout=fds32. El banco 0 contiene código común en $A000–$DFFF. El banco 1 ocupa $6000–$9FFF al arrancar; los bancos 2 y siguientes son overlays cargados en esa misma ventana de 16 KiB. Coloca el código o los datos de un overlay con directivas como #pragma fixed_bank 2.

El banco 1 se restaura desde un archivo PRG de arranque independiente de 16 384 bytes en $6000. Sin manifiesto, los archivos predeterminados usan ID 0 para el banco 1, ID 1 para el código común e ID 2 para CHR. Un manifiesto propio debe dividir el PRG de arranque en archivos de 16 384 bytes en $6000 y $A000. Para los bancos 2 y siguientes, el ID de archivo es --fds-overlay-start-id (32 por defecto) + número de banco − 2.

Al llamar a otro banco, se guarda el banco anterior y se restaura antes de volver. Las llamadas anidadas siguen la misma regla. También se intenta restaurar tras una transferencia parcialmente fallida. Si la propia restauración falla, la ejecución se detiene en el código común en lugar de volver a código sobrescrito. Los destinos del banco común y las llamadas al mismo banco evitan transferencias innecesarias.

Las llamadas al BIOS son síncronas; esta API no mantiene el bucle del juego avanzando una vez por fotograma durante una carga. Úsala desde código de control normal en el banco común, no desde un manejador de interrupciones. La continuidad de imagen y audio y la convivencia con el BIOS real requieren verificación aparte.

Estas capturas usan un programa de prueba original de la convención de llamada LoadFiles para comprobar el código CPU compilado, las transferencias de memoria y cada píxel mostrado. No verifican el BIOS real, los tiempos de transferencia de disco, el CRC ni el arranque en hardware. KUROSAKI tiene un modelo parcial del controlador FDS; una ejecución normal sin este programa de prueba no demuestra los mismos resultados de transferencia.

El primer comando del compilador que aparece abajo construye el ejemplo descargable. El comprobador Python compila las cinco lecciones con y sin optimización y las verifica en KUROSAKI. Crea y elimina un programa de prueba temporal a partir de scripts/fds_abi_fixture.py; estas comprobaciones no necesitan un BIOS externo. Ese programa de prueba no es un BIOS para juegos normales.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Con el programa de prueba original de la ABI LoadFiles, RETURN VALUE es 042; CALLBACK COUNT y BANK EXPR COUNT son 001; BANK IN CALLBACK es 002 y BANK AFTER RETURN es 001. La función y la expresión de banco se ejecutaron una vez cada una, y el banco 1 se restauró tras llamar al banco 2. No es una captura de una prueba de transferencia real de disco.

__fds_farcall: FARCALL-DEFAULT — Con el programa de prueba original de la ABI LoadFiles, RETURN VALUE es 042; CALLBACK COUNT y BANK EXPR COUNT son 001; BANK IN CALLBACK es 002 y BANK AFTER RETURN es 001. La función y la expresión de banco se ejecutaron una vez cada una, y el banco 1 se restauró tras llamar al banco 2. No es una captura de una prueba de transferencia real de disco.
FARCALL-DEFAULT — Con el programa de prueba original de la ABI LoadFiles, RETURN VALUE es 042; CALLBACK COUNT y BANK EXPR COUNT son 001; BANK IN CALLBACK es 002 y BANK AFTER RETURN es 001. La función y la expresión de banco se ejecutaron una vez cada una, y el banco 1 se restauró tras llamar al banco 2. No es una captura de una prueba de transferencia real de disco.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_overlay.h:25

Notas de la declaración (original)
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

Compara el banco solicitado con el registro de residencia. Ayuda a decidir si hace falta cargar, pero no realiza la carga.

Sintaxis

c
u8 __fds_is_bank_resident(u8 bank);

Parámetros

bank

Número de banco lógico que se compara. El arranque estándar registra 1; los overlays generados usan 2 en adelante. El banco 0 contiene código común y no es el valor inicial de este registro de residencia.

Valor devuelto

1 si un banco válido coincide con el registro y 0 en otro caso. El banco 255 (desconocido) siempre devuelve cero, aunque el registro sea 255.

Condiciones y límites

Compila con --mapper=fds. Para un mapper de cartucho, llamar a una función exclusiva de FDS distinta de __fds_available se rechaza con KQFC2420.

El arranque FDS estándar inicializa el registro de residencia a 1. __fds_load_bank() lo actualiza tras una carga correcta. Las cargas directas mediante __fds_load_file() o __fds_load_overlay() y las transferencias de banco fallidas lo ponen a 255 (desconocido). No se comprueba el contenido de la memoria; no confíes en el registro después de modificar directamente la ventana. Una rutina de reinicio propia debe gestionar la inicialización.

La captura usa la carga directa de los archivos de arranque del disco en RAM de KUROSAKI. El ejemplo no llama a rutinas BIOS ni verifica transferencias de archivos, cambios de disco o arranque en hardware. El firmware de prueba vacío no sustituye al BIOS. El ejemplo usa NMI para actualizar la pantalla y deshabilita las IRQ enmascarables, que estas consultas no necesitan.

El ejemplo de compilación genera un .fds con distribución nativa, sin archivos de arranque adicionales para la pantalla de aprobación. Esta configuración sirve para comprobar el arranque directo de KUROSAKI; el arranque de un disco en hardware requiere validación aparte.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

CURRENT BANK es 1, BANK 1 RESIDENT es 1, BANK 2 RESIDENT es 0 y OVERLAY FUNCTIONS es 2. Incluir dos funciones en el banco 2 no hace que ese banco esté residente al arrancar. En la compilación de verificación con --fds-no-overlay-table, solo el último valor cambia a cero.

__fds_is_bank_resident: RESIDENCY-FDS-DEFAULT — CURRENT BANK es 1, BANK 1 RESIDENT es 1, BANK 2 RESIDENT es 0 y OVERLAY FUNCTIONS es 2. Incluir dos funciones en el banco 2 no hace que ese banco esté residente al arrancar. En la compilación de verificación con --fds-no-overlay-table, solo el último valor cambia a cero.
RESIDENCY-FDS-DEFAULT — CURRENT BANK es 1, BANK 1 RESIDENT es 1, BANK 2 RESIDENT es 0 y OVERLAY FUNCTIONS es 2. Incluir dos funciones en el banco 2 no hace que ese banco esté residente al arrancar. En la compilación de verificación con --fds-no-overlay-table, solo el último valor cambia a cero.
__fds_is_bank_resident: RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK es 1, BANK 1 RESIDENT es 1, BANK 2 RESIDENT es 0 y OVERLAY FUNCTIONS es 2. Incluir dos funciones en el banco 2 no hace que ese banco esté residente al arrancar. En la compilación de verificación con --fds-no-overlay-table, solo el último valor cambia a cero.
RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK es 1, BANK 1 RESIDENT es 1, BANK 2 RESIDENT es 0 y OVERLAY FUNCTIONS es 2. Incluir dos funciones en el banco 2 no hace que ese banco esté residente al arrancar. En la compilación de verificación con --fds-no-overlay-table, solo el último valor cambia a cero.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_overlay.h:18

Notas de la declaración (original)
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

Convierte un banco lógico en su ID de archivo y carga la ventana de overlay $6000–$9FFF; después registra ese banco como residente.

Sintaxis

c
u8 __fds_load_bank(u8 bank);

Parámetros

bank

Banco lógico que se carga, de 1 a 254. El banco 0 es código común, no una ventana cargable; 255 se reserva para un banco residente desconocido. Para bancos a partir de 2, el ID calculado también debe estar entre 0 y 254.

Valor devuelto

Cero si tiene éxito. Devuelve 255 para el banco 0 o 255, un desbordamiento del ID de archivo o el banco 1 sin archivo de restauración. Una transferencia BIOS fallida devuelve su byte de error. Los errores de argumentos previos a la transferencia conservan el registro de residencia; los fallos de transferencia BIOS lo marcan como 255 (desconocido).

Condiciones y límites

Úsalo con --mapper=fds --fds-layout=fds32. El banco 0 contiene código común en $A000–$DFFF. El banco 1 ocupa $6000–$9FFF al arrancar; los bancos 2 y siguientes son overlays cargados en esa misma ventana de 16 KiB. Coloca el código o los datos de un overlay con directivas como #pragma fixed_bank 2.

El banco 1 se restaura desde un archivo PRG de arranque independiente de 16 384 bytes en $6000. Sin manifiesto, los archivos predeterminados usan ID 0 para el banco 1, ID 1 para el código común e ID 2 para CHR. Un manifiesto propio debe dividir el PRG de arranque en archivos de 16 384 bytes en $6000 y $A000. Para los bancos 2 y siguientes, el ID de archivo es --fds-overlay-start-id (32 por defecto) + número de banco − 2.

Con la comprobación de residencia predeterminada, un banco ya registrado devuelve cero sin leer el disco. --fds-no-overlay-guard deshabilita este atajo en __fds_load_bank(). Una carga correcta registra el banco solicitado. Ese registro no valida el contenido de la memoria.

Cargar otro banco directamente desde código en la ventana $6000–$9FFF puede sobrescribir la función en ejecución o su dirección de retorno. Este ejemplo coloca el código llamador y de visualización en #pragma fixed_bank 0. Para llamar a una función de otro banco y volver, usa __fds_overlay_farcall() o una llamada normal con tipos de función.

Las llamadas al BIOS son síncronas; esta API no mantiene el bucle del juego avanzando una vez por fotograma durante una carga. Úsala desde código de control normal en el banco común, no desde un manejador de interrupciones. La continuidad de imagen y audio y la convivencia con el BIOS real requieren verificación aparte.

Estas capturas usan un programa de prueba original de la convención de llamada LoadFiles para comprobar el código CPU compilado, las transferencias de memoria y cada píxel mostrado. No verifican el BIOS real, los tiempos de transferencia de disco, el CRC ni el arranque en hardware. KUROSAKI tiene un modelo parcial del controlador FDS; una ejecución normal sin este programa de prueba no demuestra los mismos resultados de transferencia.

El primer comando del compilador que aparece abajo construye el ejemplo descargable. El comprobador Python compila las cinco lecciones con y sin optimización y las verifica en KUROSAKI. Crea y elimina un programa de prueba temporal a partir de scripts/fds_abi_fixture.py; estas comprobaciones no necesitan un BIOS externo. Ese programa de prueba no es un BIOS para juegos normales.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Con el programa de prueba original de la ABI LoadFiles, LOAD BANK 2 STATUS, REQUIRE SAME BANK y RESTORE BANK 1 son 000. FIRST / LAST BYTE muestra 011 y 044 de los cuatro bytes transferidos; RESIDENT BANK es 001 tras restaurar. Los bancos no válidos 0 y 255 devuelven 255 cada uno. Solicitar el mismo banco no produce otra carga: las peticiones de archivo verificadas son 32 y después 0.

__fds_load_bank: BANK_LOAD-DEFAULT — Con el programa de prueba original de la ABI LoadFiles, LOAD BANK 2 STATUS, REQUIRE SAME BANK y RESTORE BANK 1 son 000. FIRST / LAST BYTE muestra 011 y 044 de los cuatro bytes transferidos; RESIDENT BANK es 001 tras restaurar. Los bancos no válidos 0 y 255 devuelven 255 cada uno. Solicitar el mismo banco no produce otra carga: las peticiones de archivo verificadas son 32 y después 0.
BANK_LOAD-DEFAULT — Con el programa de prueba original de la ABI LoadFiles, LOAD BANK 2 STATUS, REQUIRE SAME BANK y RESTORE BANK 1 son 000. FIRST / LAST BYTE muestra 011 y 044 de los cuatro bytes transferidos; RESIDENT BANK es 001 tras restaurar. Los bancos no válidos 0 y 255 devuelven 255 cada uno. Solicitar el mismo banco no produce otra carga: las peticiones de archivo verificadas son 32 y después 0.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_overlay.h:11

Notas de la declaración (original)
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

Llama a LoadFiles del BIOS usando un ID de archivo de disco en lugar de un número de banco lógico. La cabecera del archivo determina el destino.

Sintaxis

c
u8 __fds_load_overlay(u8 id);

Parámetros

id

ID de archivo de disco, de 0 a 254. No pases 255: termina la lista de archivos del BIOS. En la distribución predeterminada, el ID 32 contiene el banco 2.

Valor devuelto

Byte de error del BIOS; cero indica éxito. Tanto tras un éxito como tras un fallo, la residencia se marca como 255 (desconocida); un ID de archivo por sí solo no establece el mapeo de un banco lógico.

Condiciones y límites

Úsalo con --mapper=fds --fds-layout=fds32. El banco 0 contiene código común en $A000–$DFFF. El banco 1 ocupa $6000–$9FFF al arrancar; los bancos 2 y siguientes son overlays cargados en esa misma ventana de 16 KiB. Coloca el código o los datos de un overlay con directivas como #pragma fixed_bank 2.

Construye una lista con el ID solicitado y el terminador 255. Comprueba el destino del manifiesto para que el archivo no sobrescriba código común o en ejecución. Para overlays lógicos, prefiere __fds_load_bank(), que gestiona la residencia.

Cargar otro banco directamente desde código en la ventana $6000–$9FFF puede sobrescribir la función en ejecución o su dirección de retorno. Este ejemplo coloca el código llamador y de visualización en #pragma fixed_bank 0. Para llamar a una función de otro banco y volver, usa __fds_overlay_farcall() o una llamada normal con tipos de función.

Las llamadas al BIOS son síncronas; esta API no mantiene el bucle del juego avanzando una vez por fotograma durante una carga. Úsala desde código de control normal en el banco común, no desde un manejador de interrupciones. La continuidad de imagen y audio y la convivencia con el BIOS real requieren verificación aparte.

Estas capturas usan un programa de prueba original de la convención de llamada LoadFiles para comprobar el código CPU compilado, las transferencias de memoria y cada píxel mostrado. No verifican el BIOS real, los tiempos de transferencia de disco, el CRC ni el arranque en hardware. KUROSAKI tiene un modelo parcial del controlador FDS; una ejecución normal sin este programa de prueba no demuestra los mismos resultados de transferencia.

El primer comando del compilador que aparece abajo construye el ejemplo descargable. El comprobador Python compila las cinco lecciones con y sin optimización y las verifica en KUROSAKI. Crea y elimina un programa de prueba temporal a partir de scripts/fds_abi_fixture.py; estas comprobaciones no necesitan un BIOS externo. Ese programa de prueba no es un BIOS para juegos normales.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Con el programa de prueba original de la ABI LoadFiles, FILE 32 LOAD STATUS es 000 y los bytes extremos de los datos transferidos son 011 y 044. Tras la carga directa, UNKNOWN BANK RECORD es 255 e IS BANK 1 RESIDENT es 000. REQUIRE BANK 1 tiene éxito con 000 y RESIDENT AFTER RELOAD pasa a 001. Esto distingue la carga por ID de archivo de la gestión de un banco lógico.

__fds_load_overlay: RAW_OVERLAY-DEFAULT — Con el programa de prueba original de la ABI LoadFiles, FILE 32 LOAD STATUS es 000 y los bytes extremos de los datos transferidos son 011 y 044. Tras la carga directa, UNKNOWN BANK RECORD es 255 e IS BANK 1 RESIDENT es 000. REQUIRE BANK 1 tiene éxito con 000 y RESIDENT AFTER RELOAD pasa a 001. Esto distingue la carga por ID de archivo de la gestión de un banco lógico.
RAW_OVERLAY-DEFAULT — Con el programa de prueba original de la ABI LoadFiles, FILE 32 LOAD STATUS es 000 y los bytes extremos de los datos transferidos son 011 y 044. Tras la carga directa, UNKNOWN BANK RECORD es 255 e IS BANK 1 RESIDENT es 000. REQUIRE BANK 1 tiene éxito con 000 y RESIDENT AFTER RELOAD pasa a 001. Esto distingue la carga por ID de archivo de la gestión de un banco lógico.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_overlay.h:7

Notas de la declaración (original)
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

Llama a una función sin parámetros en un overlay y restaura el banco anterior antes de volver.

Sintaxis

c
u8 __fds_overlay_farcall(u8 bank, u16 func);

Parámetros

bank

Expresión que representa el banco. Se evalúa una vez antes de llamar, pero el banco que realmente se carga procede de la ubicación de func tras el enlazado. No llama a una dirección de un banco arbitrario elegido en tiempo de ejecución. Pasa un valor coincidente, como __bankof(func).

func

Nombre de una función declarada sin parámetros. El ejemplo pasa u8 overlay_answer(void). Se rechazan las direcciones numéricas, los punteros de función en tiempo de ejecución y las funciones que requieren argumentos. Usa una llamada normal target(a, b) si necesitas argumentos.

Valor devuelto

Resultado de ocho bits de la función de destino. Si falla la carga y se puede restaurar el banco anterior, no se llama al destino y se devuelve el byte de error de carga. Diseña la convención de resultados para poder distinguir un error de un resultado normal.

Condiciones y límites

Úsalo con --mapper=fds --fds-layout=fds32. El banco 0 contiene código común en $A000–$DFFF. El banco 1 ocupa $6000–$9FFF al arrancar; los bancos 2 y siguientes son overlays cargados en esa misma ventana de 16 KiB. Coloca el código o los datos de un overlay con directivas como #pragma fixed_bank 2.

El banco 1 se restaura desde un archivo PRG de arranque independiente de 16 384 bytes en $6000. Sin manifiesto, los archivos predeterminados usan ID 0 para el banco 1, ID 1 para el código común e ID 2 para CHR. Un manifiesto propio debe dividir el PRG de arranque en archivos de 16 384 bytes en $6000 y $A000. Para los bancos 2 y siguientes, el ID de archivo es --fds-overlay-start-id (32 por defecto) + número de banco − 2.

Al llamar a otro banco, se guarda el banco anterior y se restaura antes de volver. Las llamadas anidadas siguen la misma regla. También se intenta restaurar tras una transferencia parcialmente fallida. Si la propia restauración falla, la ejecución se detiene en el código común en lugar de volver a código sobrescrito. Los destinos del banco común y las llamadas al mismo banco evitan transferencias innecesarias.

Las llamadas al BIOS son síncronas; esta API no mantiene el bucle del juego avanzando una vez por fotograma durante una carga. Úsala desde código de control normal en el banco común, no desde un manejador de interrupciones. La continuidad de imagen y audio y la convivencia con el BIOS real requieren verificación aparte.

Estas capturas usan un programa de prueba original de la convención de llamada LoadFiles para comprobar el código CPU compilado, las transferencias de memoria y cada píxel mostrado. No verifican el BIOS real, los tiempos de transferencia de disco, el CRC ni el arranque en hardware. KUROSAKI tiene un modelo parcial del controlador FDS; una ejecución normal sin este programa de prueba no demuestra los mismos resultados de transferencia.

El primer comando del compilador que aparece abajo construye el ejemplo descargable. El comprobador Python compila las cinco lecciones con y sin optimización y las verifica en KUROSAKI. Crea y elimina un programa de prueba temporal a partir de scripts/fds_abi_fixture.py; estas comprobaciones no necesitan un BIOS externo. Ese programa de prueba no es un BIOS para juegos normales.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Con el programa de prueba original de la ABI LoadFiles, RETURN VALUE es 042; CALLBACK COUNT y BANK EXPR COUNT son 001; BANK IN CALLBACK es 002 y BANK AFTER RETURN es 001. La función y la expresión de banco se ejecutaron una vez cada una, y el banco 1 se restauró tras llamar al banco 2. No es una captura de una prueba de transferencia real de disco.

__fds_overlay_farcall: OVERLAY_CALL-DEFAULT — Con el programa de prueba original de la ABI LoadFiles, RETURN VALUE es 042; CALLBACK COUNT y BANK EXPR COUNT son 001; BANK IN CALLBACK es 002 y BANK AFTER RETURN es 001. La función y la expresión de banco se ejecutaron una vez cada una, y el banco 1 se restauró tras llamar al banco 2. No es una captura de una prueba de transferencia real de disco.
OVERLAY_CALL-DEFAULT — Con el programa de prueba original de la ABI LoadFiles, RETURN VALUE es 042; CALLBACK COUNT y BANK EXPR COUNT son 001; BANK IN CALLBACK es 002 y BANK AFTER RETURN es 001. La función y la expresión de banco se ejecutaron una vez cada una, y el banco 1 se restauró tras llamar al banco 2. No es una captura de una prueba de transferencia real de disco.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_overlay.h:23

Notas de la declaración (original)
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

Devuelve la cantidad de funciones que el enlazador coloca en bancos de overlay a partir del 2. No es una cantidad de funciones residentes ni de archivos.

Sintaxis

c
u8 __fds_overlay_function_count(void);

Valor devuelto

Byte inicial de recuento de la tabla, de 0 a 255. Es cero si no hay funciones de overlay o se usa --fds-no-overlay-table. Más de 255 entradas se rechazan con KQFC2511.

Condiciones y límites

Compila con --mapper=fds. Para un mapper de cartucho, llamar a una función exclusiva de FDS distinta de __fds_available se rechaza con KQFC2420.

La generación de código crea un marcador con valor cero; el enlazado FDS nativo lo sustituye por la tabla tras asignar bancos y direcciones. El ejemplo incluye dos funciones en el banco 2 sin llamarlas. Los datos iniciales de --fds-layout=legacy no constituyen un recuento de funciones de overlay.

La captura usa la carga directa de los archivos de arranque del disco en RAM de KUROSAKI. El ejemplo no llama a rutinas BIOS ni verifica transferencias de archivos, cambios de disco o arranque en hardware. El firmware de prueba vacío no sustituye al BIOS. El ejemplo usa NMI para actualizar la pantalla y deshabilita las IRQ enmascarables, que estas consultas no necesitan.

El ejemplo de compilación genera un .fds con distribución nativa, sin archivos de arranque adicionales para la pantalla de aprobación. Esta configuración sirve para comprobar el arranque directo de KUROSAKI; el arranque de un disco en hardware requiere validación aparte.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

CURRENT BANK es 1, BANK 1 RESIDENT es 1, BANK 2 RESIDENT es 0 y OVERLAY FUNCTIONS es 2. Incluir dos funciones en el banco 2 no hace que ese banco esté residente al arrancar. En la compilación de verificación con --fds-no-overlay-table, solo el último valor cambia a cero.

__fds_overlay_function_count: RESIDENCY-FDS-DEFAULT — CURRENT BANK es 1, BANK 1 RESIDENT es 1, BANK 2 RESIDENT es 0 y OVERLAY FUNCTIONS es 2. Incluir dos funciones en el banco 2 no hace que ese banco esté residente al arrancar. En la compilación de verificación con --fds-no-overlay-table, solo el último valor cambia a cero.
RESIDENCY-FDS-DEFAULT — CURRENT BANK es 1, BANK 1 RESIDENT es 1, BANK 2 RESIDENT es 0 y OVERLAY FUNCTIONS es 2. Incluir dos funciones en el banco 2 no hace que ese banco esté residente al arrancar. En la compilación de verificación con --fds-no-overlay-table, solo el último valor cambia a cero.
__fds_overlay_function_count: RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK es 1, BANK 1 RESIDENT es 1, BANK 2 RESIDENT es 0 y OVERLAY FUNCTIONS es 2. Incluir dos funciones en el banco 2 no hace que ese banco esté residente al arrancar. En la compilación de verificación con --fds-no-overlay-table, solo el último valor cambia a cero.
RESIDENCY-FDS-TABLE-OFF-DEFAULT — CURRENT BANK es 1, BANK 1 RESIDENT es 1, BANK 2 RESIDENT es 0 y OVERLAY FUNCTIONS es 2. Incluir dos funciones en el banco 2 no hace que ese banco esté residente al arrancar. En la compilación de verificación con --fds-no-overlay-table, solo el último valor cambia a cero.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_overlay.h:20

Notas de la declaración (original)
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

Carga un banco solo si aún no está registrado como residente. Llama antes de acceder a un recurso para evitar una transferencia innecesaria si ya está disponible.

Sintaxis

c
u8 __fds_require_bank(u8 bank);

Parámetros

bank

Banco lógico que se carga, de 1 a 254. El banco 0 es código común, no una ventana cargable; 255 se reserva para un banco residente desconocido. Para bancos a partir de 2, el ID calculado también debe estar entre 0 y 254.

Valor devuelto

Cero si tiene éxito. Devuelve 255 para el banco 0 o 255, un desbordamiento del ID de archivo o el banco 1 sin archivo de restauración. Una transferencia BIOS fallida devuelve su byte de error. Los errores de argumentos previos a la transferencia conservan el registro de residencia; los fallos de transferencia BIOS lo marcan como 255 (desconocido).

Condiciones y límites

Úsalo con --mapper=fds --fds-layout=fds32. El banco 0 contiene código común en $A000–$DFFF. El banco 1 ocupa $6000–$9FFF al arrancar; los bancos 2 y siguientes son overlays cargados en esa misma ventana de 16 KiB. Coloca el código o los datos de un overlay con directivas como #pragma fixed_bank 2.

Un banco válido que coincide con el registro devuelve cero; en otro caso se llama a __fds_load_bank(). La comparación inicial sigue activa con --fds-no-overlay-guard. El banco 255 nunca se considera residente, aunque el registro sea 255.

El banco 1 se restaura desde un archivo PRG de arranque independiente de 16 384 bytes en $6000. Sin manifiesto, los archivos predeterminados usan ID 0 para el banco 1, ID 1 para el código común e ID 2 para CHR. Un manifiesto propio debe dividir el PRG de arranque en archivos de 16 384 bytes en $6000 y $A000. Para los bancos 2 y siguientes, el ID de archivo es --fds-overlay-start-id (32 por defecto) + número de banco − 2.

Cargar otro banco directamente desde código en la ventana $6000–$9FFF puede sobrescribir la función en ejecución o su dirección de retorno. Este ejemplo coloca el código llamador y de visualización en #pragma fixed_bank 0. Para llamar a una función de otro banco y volver, usa __fds_overlay_farcall() o una llamada normal con tipos de función.

Las llamadas al BIOS son síncronas; esta API no mantiene el bucle del juego avanzando una vez por fotograma durante una carga. Úsala desde código de control normal en el banco común, no desde un manejador de interrupciones. La continuidad de imagen y audio y la convivencia con el BIOS real requieren verificación aparte.

Estas capturas usan un programa de prueba original de la convención de llamada LoadFiles para comprobar el código CPU compilado, las transferencias de memoria y cada píxel mostrado. No verifican el BIOS real, los tiempos de transferencia de disco, el CRC ni el arranque en hardware. KUROSAKI tiene un modelo parcial del controlador FDS; una ejecución normal sin este programa de prueba no demuestra los mismos resultados de transferencia.

El primer comando del compilador que aparece abajo construye el ejemplo descargable. El comprobador Python compila las cinco lecciones con y sin optimización y las verifica en KUROSAKI. Crea y elimina un programa de prueba temporal a partir de scripts/fds_abi_fixture.py; estas comprobaciones no necesitan un BIOS externo. Ese programa de prueba no es un BIOS para juegos normales.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Con el programa de prueba original de la ABI LoadFiles, LOAD BANK 2 STATUS, REQUIRE SAME BANK y RESTORE BANK 1 son 000. FIRST / LAST BYTE muestra 011 y 044 de los cuatro bytes transferidos; RESIDENT BANK es 001 tras restaurar. Los bancos no válidos 0 y 255 devuelven 255 cada uno. Solicitar el mismo banco no produce otra carga: las peticiones de archivo verificadas son 32 y después 0.

__fds_require_bank: BANK_LOAD-DEFAULT — Con el programa de prueba original de la ABI LoadFiles, LOAD BANK 2 STATUS, REQUIRE SAME BANK y RESTORE BANK 1 son 000. FIRST / LAST BYTE muestra 011 y 044 de los cuatro bytes transferidos; RESIDENT BANK es 001 tras restaurar. Los bancos no válidos 0 y 255 devuelven 255 cada uno. Solicitar el mismo banco no produce otra carga: las peticiones de archivo verificadas son 32 y después 0.
BANK_LOAD-DEFAULT — Con el programa de prueba original de la ABI LoadFiles, LOAD BANK 2 STATUS, REQUIRE SAME BANK y RESTORE BANK 1 son 000. FIRST / LAST BYTE muestra 011 y 044 de los cuatro bytes transferidos; RESIDENT BANK es 001 tras restaurar. Los bancos no válidos 0 y 255 devuelven 255 cada uno. Solicitar el mismo banco no produce otra carga: las peticiones de archivo verificadas son 32 y después 0.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_overlay.h:13

Notas de la declaración (original)
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 — Sonido por tabla de ondas de FDS

Declara las intrínsecas del compilador para onda de 64 pasos, tabla de modulación, altura, volumen y envolventes. KITAQFC genera sus implementaciones.

Construye una imagen .fds con --mapper=fds. Esta lección usa sonido sin E/S de disco. La grabación utiliza el arranque directo en RAM y el modelo de audio de KUROSAKI. El firmware vacío de prueba no implementa rutinas BIOS ni se distribuye. El arranque normal desde disco y el hardware físico requieren un entorno de arranque adecuado.

__fds_env_set

Escribe los tres argumentos brutos en $4080, $4084 y $408A, en ese orden.

Sintaxis

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

Parámetros

speed

Pese al nombre del parámetro, es el valor bruto de $4080: bit 7 volumen directo y bit 6 envolvente creciente.

gain

Valor bruto de envolvente de modulación escrito en $4084.

mode

Velocidad maestra de envolvente escrita en $408A.

Valor devuelto

Sin valor de retorno. Modifica registros de sonido o el estado del controlador.

Condiciones y límites

Construye una imagen .fds con --mapper=fds. Esta lección usa sonido sin E/S de disco. La grabación utiliza el arranque directo en RAM y el modelo de audio de KUROSAKI. El firmware vacío de prueba no implementa rutinas BIOS ni se distribuye. El arranque normal desde disco y el hardware físico requieren un entorno de arranque adecuado.

Ejemplo de uso

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

Resultado esperado

Un triángulo original de 64 pasos suena a unos 440 Hz, pausa, suena a unos 660 Hz y se detiene. La segunda nota usa nes_fds_wave_load y ajustes brutos de envolvente. Esto verifica el modelo de audio y el arranque directo, no el inicio desde disco mediante BIOS.

__fds_env_set: NTSC
NTSC
NTSC — Audio grabado

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_sound.h:18

Notas de la declaración (original)
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

Escribe la frecuencia de 12 bits en $4082/$4083 y borra los bits de control superiores de $4083.

Sintaxis

c
void __fds_freq_set(u16 freq);

Parámetros

freq

Valor del registro de frecuencia FDS de 0 a 4095. Se borran los bits de control superiores.

Valor devuelto

Sin valor de retorno. Modifica registros de sonido o el estado del controlador.

Condiciones y límites

Construye una imagen .fds con --mapper=fds. Esta lección usa sonido sin E/S de disco. La grabación utiliza el arranque directo en RAM y el modelo de audio de KUROSAKI. El firmware vacío de prueba no implementa rutinas BIOS ni se distribuye. El arranque normal desde disco y el hardware físico requieren un entorno de arranque adecuado.

Ejemplo de uso

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

Resultado esperado

Un triángulo original de 64 pasos suena a unos 440 Hz, pausa, suena a unos 660 Hz y se detiene. La segunda nota usa nes_fds_wave_load y ajustes brutos de envolvente. Esto verifica el modelo de audio y el arranque directo, no el inicio desde disco mediante BIOS.

__fds_freq_set: NTSC
NTSC
NTSC — Audio grabado

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_sound.h:13

Notas de la declaración (original)
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

Envía 32 bytes en orden al puerto de escritura de tabla de modulación $4088.

Sintaxis

c
void __fds_mod_load(const u8* mod32);

Parámetros

mod32

Treinta y dos bytes para la tabla de modulación FDS, cada uno con código de control de 0 a 7. Detén la modulación antes de cargarlos.

Valor devuelto

Sin valor de retorno. Modifica registros de sonido o el estado del controlador.

Condiciones y límites

Construye una imagen .fds con --mapper=fds. Esta lección usa sonido sin E/S de disco. La grabación utiliza el arranque directo en RAM y el modelo de audio de KUROSAKI. El firmware vacío de prueba no implementa rutinas BIOS ni se distribuye. El arranque normal desde disco y el hardware físico requieren un entorno de arranque adecuado.

Ejemplo de uso

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

Resultado esperado

Un triángulo original de 64 pasos suena a unos 440 Hz, pausa, suena a unos 660 Hz y se detiene. La segunda nota usa nes_fds_wave_load y ajustes brutos de envolvente. Esto verifica el modelo de audio y el arranque directo, no el inicio desde disco mediante BIOS.

__fds_mod_load: NTSC
NTSC
NTSC — Audio grabado

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_sound.h:11

Notas de la declaración (original)
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

Escribe 0x83 en el control disco/sonido $4023 y pone a cero el control de onda $4089 y la velocidad $408A.

Sintaxis

c
void __fds_sound_enable(void);

Valor devuelto

Sin valor de retorno. Modifica registros de sonido o el estado del controlador.

Condiciones y límites

Construye una imagen .fds con --mapper=fds. Esta lección usa sonido sin E/S de disco. La grabación utiliza el arranque directo en RAM y el modelo de audio de KUROSAKI. El firmware vacío de prueba no implementa rutinas BIOS ni se distribuye. El arranque normal desde disco y el hardware físico requieren un entorno de arranque adecuado.

Ejemplo de uso

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

Resultado esperado

Un triángulo original de 64 pasos suena a unos 440 Hz, pausa, suena a unos 660 Hz y se detiene. La segunda nota usa nes_fds_wave_load y ajustes brutos de envolvente. Esto verifica el modelo de audio y el arranque directo, no el inicio desde disco mediante BIOS.

__fds_sound_enable: NTSC
NTSC
NTSC — Audio grabado

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_sound.h:7

Notas de la declaración (original)
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

Escribe los seis bits bajos de volumen junto con 0x80 en $4080 para controlar el volumen directamente.

Sintaxis

c
void __fds_volume_set(u8 vol);

Parámetros

vol

Volumen directo FDS de 0 a 63; al enmascarar a seis bits, 64 se convierte en cero.

Valor devuelto

Sin valor de retorno. Modifica registros de sonido o el estado del controlador.

Condiciones y límites

Construye una imagen .fds con --mapper=fds. Esta lección usa sonido sin E/S de disco. La grabación utiliza el arranque directo en RAM y el modelo de audio de KUROSAKI. El firmware vacío de prueba no implementa rutinas BIOS ni se distribuye. El arranque normal desde disco y el hardware físico requieren un entorno de arranque adecuado.

Ejemplo de uso

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

Resultado esperado

Un triángulo original de 64 pasos suena a unos 440 Hz, pausa, suena a unos 660 Hz y se detiene. La segunda nota usa nes_fds_wave_load y ajustes brutos de envolvente. Esto verifica el modelo de audio y el arranque directo, no el inicio desde disco mediante BIOS.

__fds_volume_set: NTSC
NTSC
NTSC — Audio grabado

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_sound.h:15

Notas de la declaración (original)
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

Habilita escrituras de onda, copia 64 bytes a $4040..$407F y restablece $4089 a cero.

Sintaxis

c
void __fds_wave_load(const u8* wave64);

Parámetros

wave64

Sesenta y cuatro bytes de onda FDS, cada uno con amplitud de seis bits de 0 a 63. No se comprueba NULL.

Valor devuelto

Sin valor de retorno. Modifica registros de sonido o el estado del controlador.

Condiciones y límites

Construye una imagen .fds con --mapper=fds. Esta lección usa sonido sin E/S de disco. La grabación utiliza el arranque directo en RAM y el modelo de audio de KUROSAKI. El firmware vacío de prueba no implementa rutinas BIOS ni se distribuye. El arranque normal desde disco y el hardware físico requieren un entorno de arranque adecuado.

Ejemplo de uso

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

Resultado esperado

Un triángulo original de 64 pasos suena a unos 440 Hz, pausa, suena a unos 660 Hz y se detiene. La segunda nota usa nes_fds_wave_load y ajustes brutos de envolvente. Esto verifica el modelo de audio y el arranque directo, no el inicio desde disco mediante BIOS.

__fds_wave_load: NTSC
NTSC
NTSC — Audio grabado

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/fds_sound.h:9

Notas de la declaración (original)
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 — Funciones intrínsecas del compilador

__nes_audio_vblank_tick

Rutina NMI que avanza los retardos BGM y SFX y actualiza la APU según quién controle cada canal en ese momento.

Sintaxis

c
void __nes_audio_vblank_tick(void);

Valor devuelto

No devuelve ningún valor.

Condiciones y límites

Incluye audio_vblank.h y añade audio_vblank.c a la compilación exactamente una vez. El compilador llama a su rutina de enlace una vez desde el manejador NMI, tanto si es el predeterminado como si es uno propio. Habilita la NMI del PPU. El bucle principal no llama a ninguna rutina de actualización musical.

Este controlador de cuatro canales está ajustado para NTSC: CH1=pulso 1, CH2=pulso 2, CH3=triángulo y CH4=ruido, con índices del 0 al 3. No reproduce DMC ni audio de expansión. No lo ejecutes junto con código que escriba directamente en la APU, como los efectos de audio.c. No incluye una tabla de tonos PAL ni corrección del tempo para PAL.

No llames a esta función directamente desde la aplicación. Al enlazar la biblioteca musical, el compilador la conecta a la NMI. Su rutina guarda A/X/Y y no usa el área temporal compartida para expresiones. La rutina y las tablas de tonos residen en ROM fija; nunca cambia de banco ROM dentro de la NMI. Esta conservación del estado solo cubre la rutina musical; un manejador NMI propio sigue siendo responsable de preservar el estado que utilicen sus demás tareas.

Cada registro contiene cinco bytes: delay, CH1, CH2, CH3, CH4. Un retardo de 1 a 255 indica cuántos ticks de NMI dura el nuevo estado. Cero finaliza la reproducción e ignora los otros cuatro bytes. Las notas de 0 a 71 corresponden a C2–B7 (24=C4). Los valores de ruido de 0 a 15 seleccionan un periodo en modo largo y los de 16 a 31, el modo corto. En cualquier canal, 255=NES_AUDIO_HOLD conserva su estado y 254=NES_AUDIO_STOP lo silencia.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo reproduce una vez una frase original de cuatro canales. La música continúa mientras el programa principal realiza cálculos enteros durante 90 fotogramas sin rellenar la cola. «PHRASE COMPLETE / SILENT» y «UNDERRUNS 0 / MATH OK» indican que la reproducción terminó, que no faltaron datos en la cola y que los cálculos dieron los resultados esperados. Escucha el audio capturado para comprobar la melodía y el acompañamiento de pulsos, el bajo triangular y el ruido. El final queda en silencio. La imagen muestra los resultados de control; el sonido se evalúa con el audio.

__nes_audio_vblank_tick: NROM
NROM
NROM — Audio grabado

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/audio_vblank.h:70

Notas de la declaración (original)
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

Escribe todo el byte de atributos empaquetados en 0x23C0 + (y>>2)*8 + (x>>2) de la tabla 0. X/Y son coordenadas de tiles que identifican un área de atributos de 4×4 tiles. Sustituye los cuatro campos de paleta de dos bits; no combina solo un cuadrante seleccionado.

Sintaxis

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

Parámetros

x / y

Coordenadas de tiles, no píxeles. Use x=0..31 e y=0..29 para el mapa visible de 32×30 tiles; cada tile ocupa 8×8 píxeles. Los argumentos de un byte no se limitan automáticamente y pueden escribir fuera del área prevista.

attr

Byte de atributos empaquetados: bits 0–1 para los 2×2 tiles superiores izquierdos, 2–3 superiores derechos, 4–5 inferiores izquierdos y 6–7 inferiores derechos. Cada campo elige paleta 0–3. 0x55, 0xAA y 0xFF eligen paleta 1, 2 o 3 en los cuatro cuadrantes.

Valor devuelto

Ninguno (void).

Condiciones y límites

El acceso directo a PPU requiere desactivar el renderizado o completar toda la operación dentro de un VBlank seguro. Estas funciones no esperan, encolan, bloquean NMI ni restauran el desplazamiento. Evite que una interrupción cambie el latch o la dirección compartidos durante la llamada. El bit 2 de PPUCTRL selecciona un incremento de destino de 1 o 32 por byte. La PPU gestiona los espejos y el retorno de direcciones; no se puede escribir en CHR-ROM. Restablezca el desplazamiento deseado antes de renderizar.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__attr_set: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:134

Notas de la declaración (original)
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

Selecciona la tabla lógica nt & 3, reinicia el latch PPU y escribe un byte completo de atributos en 0x23C0 + (nt & 3)*0x400 + (y>>2)*8 + (x>>2). Sobrescribe todos sus cuadrantes de paleta. La selección sigue el mirroring actual del cartucho.

Sintaxis

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

Parámetros

nt

Selector de tabla lógica. Solo usa los dos bits inferiores (nt & 3): 4 selecciona la tabla 0 y 7 la tabla 3. No asigna almacenamiento independiente ni cambia el mirroring del mapper.

x / y

Coordenadas de tiles, no píxeles. Use x=0..31 e y=0..29 para el mapa visible de 32×30 tiles; cada tile ocupa 8×8 píxeles. Los argumentos de un byte no se limitan automáticamente y pueden escribir fuera del área prevista.

attr

Byte de atributos empaquetados: bits 0–1 para los 2×2 tiles superiores izquierdos, 2–3 superiores derechos, 4–5 inferiores izquierdos y 6–7 inferiores derechos. Cada campo elige paleta 0–3. 0x55, 0xAA y 0xFF eligen paleta 1, 2 o 3 en los cuatro cuadrantes.

Valor devuelto

Ninguno (void).

Condiciones y límites

El acceso directo a PPU requiere desactivar el renderizado o completar toda la operación dentro de un VBlank seguro. Estas funciones no esperan, encolan, bloquean NMI ni restauran el desplazamiento. Evite que una interrupción cambie el latch o la dirección compartidos durante la llamada. El bit 2 de PPUCTRL selecciona un incremento de destino de 1 o 32 por byte. La PPU gestiona los espejos y el retorno de direcciones; no se puede escribir en CHR-ROM. Restablezca el desplazamiento deseado antes de renderizar.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__attr_set_nt: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:136

Notas de la declaración (original)
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

Obtiene el número de banco ROM lógico asignado a un objeto de datos o función con nombre. Resuelve información de ubicación; no consulta el banco activo ni desreferencia un puntero.

Sintaxis

c
u8 bank = __bankof(symbol);

Parámetros

symbol

Nombre de un objeto ROM o función cuya ubicación conoce el compilador o enlazador. Una variable puntero no representa aquí al objeto apuntado; no se admiten expresiones de ejecución como nombres de símbolo.

Valor devuelto

Número de banco u8. Cero puede identificar ROM común, pero no es un selector de banco válido en todos los destinos.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo comprueba que payload y callback están en el banco 3. Esto verifica metadatos de ubicación; las lecturas y comprobaciones del callback verifican aparte el comportamiento durante la ejecución.

El programa completo enseña a identificar datos con un banco y una dirección cercana, a llamar callbacks entre bancos y a distinguir el registro de la biblioteca del mapa real. Todos los números se muestran en hexadecimal. BANK IN y BANK OUT deben ser 02; BYTE HEX, D3; WORD HEX, 5C7A; FAILED CHECKS y FIRST FAILURE, 00. CGB y FC muestran texto azul; DMG, negro.

CALL COUNT muestra 02: la macro far_call y __farcall invocan el callback una vez cada uno.

__bankof: MMC3
MMC3

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:77

Notas de la declaración (original)
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

Selecciona un banco PRG lógico mediante la rutina del compilador específica del mapper y actualiza su estado interno. El cambio persiste, sin actualizar el registro independiente de la biblioteca bank.

Sintaxis

c
void __bankswitch(u8 bank);

Parámetros

bank

Número de banco ROM de 8 bits sin signo. En una llamada debe coincidir con la ubicación de la función; en una lectura selecciona el origen. Usa el intervalo válido para el cartucho.

Valor devuelto

Ninguno (void).

Condiciones y límites

Un cambio de banco persistente no debe retirar el código en ejecución ni su dirección de retorno. Coloca esta llamada, la propia bank_switch cuando se use y el código posterior en el banco común o fijo 0, como hace el ejemplo.

El registro de la biblioteca empieza en cero y no detecta el mapa inicial del cartucho. Inicialízalo con bank_switch. Las intrínsecas y las escrituras directas al mapper no actualizan este registro separado; bank_get_current no permite verificar el mapa real del hardware.

Los bancos FC son números lógicos del compilador. Cero designa ROM común, no un destino de cambio; la rutina lo convierte en banco 1. SUROM512 admite 1–30: una constante fuera de rango pasada directamente a la intrínseca de cambio produce un error de compilación; un valor inválido en ejecución selecciona 1 y activa el estado de fallo del mapper. Lee la ROM común directamente, sin pedir un cambio al banco cero.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

bank_switch(2) registra y selecciona inicialmente el banco 2. Después, el cambio directo selecciona el banco 1 y lee A1 de home1 mientras bank_get_current sigue indicando 2; al volver, home2 devuelve B2 otra vez.

El programa completo enseña a identificar datos con un banco y una dirección cercana, a llamar callbacks entre bancos y a distinguir el registro de la biblioteca del mapa real. Todos los números se muestran en hexadecimal. BANK IN y BANK OUT deben ser 02; BYTE HEX, D3; WORD HEX, 5C7A; FAILED CHECKS y FIRST FAILURE, 00. CGB y FC muestran texto azul; DMG, negro.

CALL COUNT muestra 02: la macro far_call y __farcall invocan el callback una vez cada uno.

__bankswitch: MMC3
MMC3

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:79

Notas de la declaración (original)
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

Borra el indicador elegido a 0 sin borrar el resto del byte. Úsalo para quitar una condición guardada, como un estado temporal. Volver a borrar el mismo bit lo mantiene en 0.

Sintaxis

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

Parámetros

base

Puntero al primer byte del array de indicadores en memoria accesible por la CPU. La llamada no reserva ni inicializa el array. Usa RAM escribible para activar, borrar e invertir; la operación de prueba solo lee.

bit

Índice de bit u16 desde cero (0–65535), no un desplazamiento de byte ni una máscara. El índice 0 selecciona 0x01 en el byte 0; el 7, 0x80 en el byte 0; el 8, 0x01 en el byte 1. En general, el desplazamiento es index / 8 y la máscara 1 << (index % 8). Para un array de N bytes, exige index < 8*N y un rango válido de direcciones de CPU.

Valor devuelto

Ninguno (void).

Condiciones y límites

El código que llama debe proporcionar espacio suficiente. No hay argumento de longitud, recorte de límites, cambio de banco ni transferencia gráfica automática. Un número de bit grande puede seleccionar un byte muy lejos del array reservado. Mantén estable el mapeo de memoria; no uses estas funciones de indicadores RAM como atajo para registros de hardware cuyas lecturas o escrituras tengan efectos secundarios.

Activar, borrar e invertir leen un byte, modifican el bit y lo escriben de nuevo. Esta secuencia no es atómica. Coordina las escrituras del bucle principal y de los manejadores de interrupción para no perder una actualización intermedia de otro bit del mismo byte.

El código FC puede insertar un índice constante en línea o llamar a una función auxiliar generada. Ambas formas usan la misma numeración y máscara de resultado. Las auxiliares comparten almacenamiento de argumentos y temporal; no vuelvas a entrar desde NMI/IRQ mientras otra auxiliar se esté ejecutando.

Ejemplo de uso

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.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa completo llega a este paso con el byte 0 en 0xA6. Borrar el bit 2 lo cambia a 0xA2 (binario 10100010, decimal 162); CLEAR BIT2 debe mostrar 162. Borrarlo otra vez no cambia el resultado. Los demás indicadores se conservan en ambas llamadas.

Valores esperados en FC, en decimal: TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE procede de activar el índice 9 en el segundo byte, pasando de 0x80 a 0x82 sin modificar el byte 0. La captura FC muestra texto azul sobre negro.

El programa comprueba cada transición mostrada e indica el número de fallos. Activar, borrar e invertir no devuelven valores; sus filas muestran el byte RAM resultante. Otras pruebas en emulador comparan los 520 bytes de búferes con zonas de protección en 136 casos por plataforma, incluidos índices constantes y en ejecución que cruzan límites de byte y de 256 bytes, y las ocho máscaras no nulas posibles. Son pruebas del estado de RAM, no una demostración gráfica. No se han probado hardware físico ni escrituras concurrentes desde interrupciones.

__bit_clear: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:45

Notas de la declaración (original)
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

Activa el indicador elegido a 1 conservando los otros siete bits del byte. Por ejemplo, marca una misión completada o un objeto recogido. Activar un bit que ya vale 1 lo deja igual; no es una conmutación.

Sintaxis

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

Parámetros

base

Puntero al primer byte del array de indicadores en memoria accesible por la CPU. La llamada no reserva ni inicializa el array. Usa RAM escribible para activar, borrar e invertir; la operación de prueba solo lee.

bit

Índice de bit u16 desde cero (0–65535), no un desplazamiento de byte ni una máscara. El índice 0 selecciona 0x01 en el byte 0; el 7, 0x80 en el byte 0; el 8, 0x01 en el byte 1. En general, el desplazamiento es index / 8 y la máscara 1 << (index % 8). Para un array de N bytes, exige index < 8*N y un rango válido de direcciones de CPU.

Valor devuelto

Ninguno (void).

Condiciones y límites

El código que llama debe proporcionar espacio suficiente. No hay argumento de longitud, recorte de límites, cambio de banco ni transferencia gráfica automática. Un número de bit grande puede seleccionar un byte muy lejos del array reservado. Mantén estable el mapeo de memoria; no uses estas funciones de indicadores RAM como atajo para registros de hardware cuyas lecturas o escrituras tengan efectos secundarios.

Activar, borrar e invertir leen un byte, modifican el bit y lo escriben de nuevo. Esta secuencia no es atómica. Coordina las escrituras del bucle principal y de los manejadores de interrupción para no perder una actualización intermedia de otro bit del mismo byte.

El código FC puede insertar un índice constante en línea o llamar a una función auxiliar generada. Ambas formas usan la misma numeración y máscara de resultado. Las auxiliares comparten almacenamiento de argumentos y temporal; no vuelvas a entrar desde NMI/IRQ mientras otra auxiliar se esté ejecutando.

Ejemplo de uso

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.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Activa el bit 1 de 0xA4: 10100100 | 00000010 pasa a 10100110, es decir, 0xA6 (166 decimal). SET BIT1 debe mostrar 166. El ejemplo vuelve a activarlo y comprueba que el byte sigue en 0xA6, mostrando que repetir la activación no invierte el indicador.

Valores esperados en FC, en decimal: TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE procede de activar el índice 9 en el segundo byte, pasando de 0x80 a 0x82 sin modificar el byte 0. La captura FC muestra texto azul sobre negro.

El programa comprueba cada transición mostrada e indica el número de fallos. Activar, borrar e invertir no devuelven valores; sus filas muestran el byte RAM resultante. Otras pruebas en emulador comparan los 520 bytes de búferes con zonas de protección en 136 casos por plataforma, incluidos índices constantes y en ejecución que cruzan límites de byte y de 256 bytes, y las ocho máscaras no nulas posibles. Son pruebas del estado de RAM, no una demostración gráfica. No se han probado hardware físico ni escrituras concurrentes desde interrupciones.

__bit_set: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:43

Notas de la declaración (original)
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

Lee un indicador de un array de bits empaquetados sin modificarlo. Así, ocho indicadores independientes de encendido/apagado comparten cada byte de RAM. El resultado indica si el bit elegido está desactivado o activado; su representación numérica depende de la plataforma.

Sintaxis

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

Parámetros

base

Puntero al primer byte del array de indicadores en memoria accesible por la CPU. La llamada no reserva ni inicializa el array. Usa RAM escribible para activar, borrar e invertir; la operación de prueba solo lee.

bit

Índice de bit u16 desde cero (0–65535), no un desplazamiento de byte ni una máscara. El índice 0 selecciona 0x01 en el byte 0; el 7, 0x80 en el byte 0; el 8, 0x01 en el byte 1. En general, el desplazamiento es index / 8 y la máscara 1 << (index % 8). Para un array de N bytes, exige index < 8*N y un rango válido de direcciones de CPU.

Valor devuelto

u8: 0 si el bit está desactivado; si no, su máscara: 1, 2, 4, 8, 16, 32, 64 o 128. El resultado es base[bit >> 3] & (1 << (bit & 7)). Por ejemplo, probar el bit 7 activado devuelve 128. Usa result != 0 para comprobar el indicador; comparar solo con 1 omitiría los de otras posiciones.

Condiciones y límites

El código que llama debe proporcionar espacio suficiente. No hay argumento de longitud, recorte de límites, cambio de banco ni transferencia gráfica automática. Un número de bit grande puede seleccionar un byte muy lejos del array reservado. Mantén estable el mapeo de memoria; no uses estas funciones de indicadores RAM como atajo para registros de hardware cuyas lecturas o escrituras tengan efectos secundarios.

El código FC puede insertar un índice constante en línea o llamar a una función auxiliar generada. Ambas formas usan la misma numeración y máscara de resultado. Las auxiliares comparten almacenamiento de argumentos y temporal; no vuelvas a entrar desde NMI/IRQ mientras otra auxiliar se esté ejecutando.

Ejemplo de uso

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.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Empieza con el byte 0 en 0xA4 (binario 10100100). Probar el bit 1 devuelve 0; probar el bit 7 devuelve 1 en GB o 128 en FC. El ejemplo también convierte ese resultado no nulo a un indicador común y comprueba que el byte original no cambia. Mira TEST OFF y TEST ON en la pantalla de abajo.

Valores esperados en FC, en decimal: TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE procede de activar el índice 9 en el segundo byte, pasando de 0x80 a 0x82 sin modificar el byte 0. La captura FC muestra texto azul sobre negro.

El programa comprueba cada transición mostrada e indica el número de fallos. Activar, borrar e invertir no devuelven valores; sus filas muestran el byte RAM resultante. Otras pruebas en emulador comparan los 520 bytes de búferes con zonas de protección en 136 casos por plataforma, incluidos índices constantes y en ejecución que cruzan límites de byte y de 256 bytes, y las ocho máscaras no nulas posibles. Son pruebas del estado de RAM, no una demostración gráfica. No se han probado hardware físico ni escrituras concurrentes desde interrupciones.

__bit_test: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:41

Notas de la declaración (original)
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

Invierte el indicador elegido: 0 pasa a 1 y 1 a 0. Conserva los demás bits. Dos llamadas sobre el mismo bit restauran su estado original, siempre que nada lo escriba entre ambas.

Sintaxis

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

Parámetros

base

Puntero al primer byte del array de indicadores en memoria accesible por la CPU. La llamada no reserva ni inicializa el array. Usa RAM escribible para activar, borrar e invertir; la operación de prueba solo lee.

bit

Índice de bit u16 desde cero (0–65535), no un desplazamiento de byte ni una máscara. El índice 0 selecciona 0x01 en el byte 0; el 7, 0x80 en el byte 0; el 8, 0x01 en el byte 1. En general, el desplazamiento es index / 8 y la máscara 1 << (index % 8). Para un array de N bytes, exige index < 8*N y un rango válido de direcciones de CPU.

Valor devuelto

Ninguno (void).

Condiciones y límites

El código que llama debe proporcionar espacio suficiente. No hay argumento de longitud, recorte de límites, cambio de banco ni transferencia gráfica automática. Un número de bit grande puede seleccionar un byte muy lejos del array reservado. Mantén estable el mapeo de memoria; no uses estas funciones de indicadores RAM como atajo para registros de hardware cuyas lecturas o escrituras tengan efectos secundarios.

Activar, borrar e invertir leen un byte, modifican el bit y lo escriben de nuevo. Esta secuencia no es atómica. Coordina las escrituras del bucle principal y de los manejadores de interrupción para no perder una actualización intermedia de otro bit del mismo byte.

El código FC puede insertar un índice constante en línea o llamar a una función auxiliar generada. Ambas formas usan la misma numeración y máscara de resultado. Las auxiliares comparten almacenamiento de argumentos y temporal; no vuelvas a entrar desde NMI/IRQ mientras otra auxiliar se esté ejecutando.

Ejemplo de uso

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.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa completo entra con 0xA2. Invertir el bit 7 produce 0x22 (34 decimal), por lo que TOGGLE BIT7 muestra 034. Invertirlo otra vez restaura 0xA2 y RESTORE muestra 162. Ambas observaciones demuestran los dos sentidos de la inversión conservando los demás bits.

Valores esperados en FC, en decimal: TEST OFF 000, TEST ON 128, SET BIT1 166, CLEAR BIT2 162, TOGGLE BIT7 034, RESTORE 162, NEXT BYTE 130, FAILED 000. NEXT BYTE procede de activar el índice 9 en el segundo byte, pasando de 0x80 a 0x82 sin modificar el byte 0. La captura FC muestra texto azul sobre negro.

El programa comprueba cada transición mostrada e indica el número de fallos. Activar, borrar e invertir no devuelven valores; sus filas muestran el byte RAM resultante. Otras pruebas en emulador comparan los 520 bytes de búferes con zonas de protección en 136 casos por plataforma, incluidos índices constantes y en ejecución que cruzan límites de byte y de 256 bytes, y las ocho máscaras no nulas posibles. Son pruebas del estado de RAM, no una demostración gráfica. No se han probado hardware físico ni escrituras concurrentes desde interrupciones.

__bit_toggle: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:47

Notas de la declaración (original)
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

Selecciona un banco CHR. En MMC3 selecciona R0 y cambia la ventana de 2 KiB en PPU $0000–$07FF. En CNROM cambia toda la región de 8 KiB en $0000–$1FFF.

Sintaxis

c
void __chr_bank_set(u8 bank);

Parámetros

bank

En CNROM es un número de banco CHR de 8 KiB. En MMC3 es un número de banco de 1 KiB que selecciona una ventana de 2 KiB: se ignora el bit bajo, por lo que 2 y 3 seleccionan el par de bancos de 1 KiB 2 y 3. Los bancos disponibles dependen de la capacidad de CHR ROM; la API no comprueba el rango.

Valor devuelto

No devuelve ningún valor.

Condiciones y límites

Las declaraciones de las funciones intrínsecas están en intrinsics.h. Incluye nes_game.h para las macros nes_mapper_irq_*. Selecciona la placa al compilar con --mapper.

Escribir 0 o 1 en el registro de selección de banco MMC3 también borra el bit 6 de modo PRG y el bit 7 de inversión CHR. No lo combines con una distribución PRG/CHR que necesite mantenerlos activados. Cambiar durante el renderizado afecta a las siguientes lecturas de patrones. El ejemplo comprueba con el renderizado desactivado y restaura los bancos de la fuente antes de mostrar resultados.

Estas tres funciones intrínsecas admiten CNROM y MMC3. Otras placas se rechazan al compilar. SUROM también usa el control CHR para seleccionar la región PRG externa y no permite estas operaciones CHR.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo MMC3 muestra marcadores CHR 19/20, 21/22 y 23/24. Las lecturas CIRAM dan A B A B con mirroring vertical y A A B B con horizontal. Los recuentos IRQ son 01→02→03→03: cada una de las tres primeras habilitaciones explícitas permite una interrupción; tras deshabilitar, el recuento no cambia. Cada etapa espera seis fotogramas.

__chr_bank_set: MMC3-DEFAULT
MMC3-DEFAULT

Programa de ejemplo completo

Comando de compilación

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

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

La lección CNROM muestra los marcadores 33, 25 y 41 después de SET 2, SET0 1 y SET1 3. Lee bytes de marcadores originales a través del PPU para demostrar que los tres nombres cambian toda la región de patrones de 8 KiB.

__chr_bank_set: CNROM-DEFAULT
CNROM-DEFAULT

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:316

Notas de la declaración (original)
Select a CNROM bank or MMC3 register 0 bank; unsupported mapper profiles produce a compile error.
__chr_bank_set0

Selecciona MMC3 R0 y cambia la ventana de 2 KiB en PPU $0000–$07FF. Equivale a __chr_bank_set. En CNROM cambia toda la región de 8 KiB.

Sintaxis

c
void __chr_bank_set0(u8 bank);

Parámetros

bank

En CNROM es un número de banco CHR de 8 KiB. En MMC3 es un número de banco de 1 KiB que selecciona una ventana de 2 KiB: se ignora el bit bajo, por lo que 2 y 3 seleccionan el par de bancos de 1 KiB 2 y 3. Los bancos disponibles dependen de la capacidad de CHR ROM; la API no comprueba el rango.

Valor devuelto

No devuelve ningún valor.

Condiciones y límites

Las declaraciones de las funciones intrínsecas están en intrinsics.h. Incluye nes_game.h para las macros nes_mapper_irq_*. Selecciona la placa al compilar con --mapper.

Escribir 0 o 1 en el registro de selección de banco MMC3 también borra el bit 6 de modo PRG y el bit 7 de inversión CHR. No lo combines con una distribución PRG/CHR que necesite mantenerlos activados. Cambiar durante el renderizado afecta a las siguientes lecturas de patrones. El ejemplo comprueba con el renderizado desactivado y restaura los bancos de la fuente antes de mostrar resultados.

Estas tres funciones intrínsecas admiten CNROM y MMC3. Otras placas se rechazan al compilar. SUROM también usa el control CHR para seleccionar la región PRG externa y no permite estas operaciones CHR.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo MMC3 muestra marcadores CHR 19/20, 21/22 y 23/24. Las lecturas CIRAM dan A B A B con mirroring vertical y A A B B con horizontal. Los recuentos IRQ son 01→02→03→03: cada una de las tres primeras habilitaciones explícitas permite una interrupción; tras deshabilitar, el recuento no cambia. Cada etapa espera seis fotogramas.

__chr_bank_set0: MMC3-DEFAULT
MMC3-DEFAULT

Programa de ejemplo completo

Comando de compilación

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

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

La lección CNROM muestra los marcadores 33, 25 y 41 después de SET 2, SET0 1 y SET1 3. Lee bytes de marcadores originales a través del PPU para demostrar que los tres nombres cambian toda la región de patrones de 8 KiB.

__chr_bank_set0: CNROM-DEFAULT
CNROM-DEFAULT

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:318

Notas de la declaración (original)
Use the same CNROM/MMC3 register 0 selection as __chr_bank_set.
__chr_bank_set1

Selecciona MMC3 R1 y cambia la ventana de 2 KiB en PPU $0800–$0FFF sin cambiar R0. CNROM no tiene una segunda ventana independiente; cambia toda la región de 8 KiB.

Sintaxis

c
void __chr_bank_set1(u8 bank);

Parámetros

bank

En CNROM es un número de banco CHR de 8 KiB. En MMC3 es un número de banco de 1 KiB que selecciona una ventana de 2 KiB: se ignora el bit bajo, por lo que 2 y 3 seleccionan el par de bancos de 1 KiB 2 y 3. Los bancos disponibles dependen de la capacidad de CHR ROM; la API no comprueba el rango.

Valor devuelto

No devuelve ningún valor.

Condiciones y límites

Las declaraciones de las funciones intrínsecas están en intrinsics.h. Incluye nes_game.h para las macros nes_mapper_irq_*. Selecciona la placa al compilar con --mapper.

Escribir 0 o 1 en el registro de selección de banco MMC3 también borra el bit 6 de modo PRG y el bit 7 de inversión CHR. No lo combines con una distribución PRG/CHR que necesite mantenerlos activados. Cambiar durante el renderizado afecta a las siguientes lecturas de patrones. El ejemplo comprueba con el renderizado desactivado y restaura los bancos de la fuente antes de mostrar resultados.

Estas tres funciones intrínsecas admiten CNROM y MMC3. Otras placas se rechazan al compilar. SUROM también usa el control CHR para seleccionar la región PRG externa y no permite estas operaciones CHR.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo MMC3 muestra marcadores CHR 19/20, 21/22 y 23/24. Las lecturas CIRAM dan A B A B con mirroring vertical y A A B B con horizontal. Los recuentos IRQ son 01→02→03→03: cada una de las tres primeras habilitaciones explícitas permite una interrupción; tras deshabilitar, el recuento no cambia. Cada etapa espera seis fotogramas.

__chr_bank_set1: MMC3-DEFAULT
MMC3-DEFAULT

Programa de ejemplo completo

Comando de compilación

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

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

La lección CNROM muestra los marcadores 33, 25 y 41 después de SET 2, SET0 1 y SET1 3. Lee bytes de marcadores originales a través del PPU para demostrar que los tres nombres cambian toda la región de patrones de 8 KiB.

__chr_bank_set1: CNROM-DEFAULT
CNROM-DEFAULT

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:320

Notas de la declaración (original)
Select MMC3 register 1, or the same full CNROM bank register; this is mapper-specific banking.
__copy16

Copia exactamente 16 bytes de src a dst, sin argumento de longitud. Por ejemplo, permite copiar un registro de 16 bytes o un tile de GB/NES situado en memoria accesible por la CPU. El 16 indica bytes, no bits; la llamada no carga gráficos en el hardware de vídeo.

Sintaxis

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

Parámetros

dst

Puntero al primer byte escribible del destino. Reserva espacio contiguo para toda la operación; la función no asigna un búfer.

src

Puntero al primer byte legible del origen. Todo el intervalo debe permanecer accesible y sin cambios durante la copia.

Valor devuelto

Ninguno (void).

Condiciones y límites

Usa regiones de origen y destino que no se solapen. Estas rutinas copian hacia delante y no ofrecen la seguridad de memmove para zonas superpuestas; escribir sobre bytes del origen aún no leídos puede corromper la copia.

La operación termina antes de devolver el control. No cambia bancos de ROM/RAM ni recorta el intervalo al límite del búfer. Mantén estable el mapa de memoria, usa regiones válidas y coordina el acceso con las interrupciones que utilicen los mismos búferes.

En FC, dst es una dirección de CPU, no de PPU. Copiar a RAM de CPU no actualiza CHR, tablas de nombres ni paletas; usa las API de PPU/VRAM para cargarlas. Las rutinas auxiliares generadas comparten argumentos y temporales: no vuelvas a entrar en ellas desde NMI/IRQ mientras se ejecuta otra llamada.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo usa __copy16 para copiar 16 bytes con un patrón conocido desde el desplazamiento 1. memory_check compara los arrays completos de origen y destino; la fila COPY16 debe mostrar 016. El primer byte queda como protección y también se comprueba que los bytes posteriores al bloque no cambien.

El programa completo inicializa dos búferes de 264 bytes antes de cada operación y comprueba todos los bytes del origen, del destino y de las zonas de protección. Debe mostrar COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 y FAILED 000. WORD/BYTE indican el tamaño del contador, no el de los elementos copiados. Los números proceden de las comprobaciones del ejemplo; estas funciones void no devuelven valores.

Las capturas muestran letras negras sobre blanco en DMG, azules sobre blanco en CGB y azules sobre negro en FC. El ejemplo verifica operaciones de RAM; el texto presenta sus resultados, no una primitiva de dibujo. Otras pruebas en emulador cubren 88 casos por plataforma, con longitudes constantes y calculadas en ejecución de 0, 1, 16, 17, 32, 33, 64, 65 y 255 bytes, más 256 y 257 para los contadores de 16 bits. No se ha probado en hardware real.

__copy16: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:27

Notas de la declaración (original)
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

Copia exactamente 32 bytes de src a dst, sin argumento de longitud. Úsalo para un registro fijo de 32 bytes o dos patrones de tile contiguos de 16 bytes en memoria accesible por la CPU. Copia byte a byte hacia direcciones crecientes; no realiza una lectura única de 32 bits ni cambia de banco.

Sintaxis

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

Parámetros

dst

Puntero al primer byte escribible del destino. Reserva espacio contiguo para toda la operación; la función no asigna un búfer.

src

Puntero al primer byte legible del origen. Todo el intervalo debe permanecer accesible y sin cambios durante la copia.

Valor devuelto

Ninguno (void).

Condiciones y límites

Usa regiones de origen y destino que no se solapen. Estas rutinas copian hacia delante y no ofrecen la seguridad de memmove para zonas superpuestas; escribir sobre bytes del origen aún no leídos puede corromper la copia.

La operación termina antes de devolver el control. No cambia bancos de ROM/RAM ni recorta el intervalo al límite del búfer. Mantén estable el mapa de memoria, usa regiones válidas y coordina el acceso con las interrupciones que utilicen los mismos búferes.

En FC, dst es una dirección de CPU, no de PPU. Copiar a RAM de CPU no actualiza CHR, tablas de nombres ni paletas; usa las API de PPU/VRAM para cargarlas. Las rutinas auxiliares generadas comparten argumentos y temporales: no vuelvas a entrar en ellas desde NMI/IRQ mientras se ejecuta otra llamada.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo usa __copy32 para copiar 32 bytes con un patrón conocido desde el desplazamiento 1. memory_check compara los arrays completos de origen y destino; la fila COPY32 debe mostrar 032. El primer byte queda como protección y también se comprueba que los bytes posteriores al bloque no cambien.

El programa completo inicializa dos búferes de 264 bytes antes de cada operación y comprueba todos los bytes del origen, del destino y de las zonas de protección. Debe mostrar COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 y FAILED 000. WORD/BYTE indican el tamaño del contador, no el de los elementos copiados. Los números proceden de las comprobaciones del ejemplo; estas funciones void no devuelven valores.

Las capturas muestran letras negras sobre blanco en DMG, azules sobre blanco en CGB y azules sobre negro en FC. El ejemplo verifica operaciones de RAM; el texto presenta sus resultados, no una primitiva de dibujo. Otras pruebas en emulador cubren 88 casos por plataforma, con longitudes constantes y calculadas en ejecución de 0, 1, 16, 17, 32, 33, 64, 65 y 255 bytes, más 256 y 257 para los contadores de 16 bits. No se ha probado en hardware real.

__copy32: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:29

Notas de la declaración (original)
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

Calcula un producto escalar sin signo de dos términos.

Sintaxis

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

Parámetros

a0

Palabra sin signo de 0 a 65535.

a1

Palabra sin signo de 0 a 65535.

b0

Coeficiente sin signo de 0 a 255.

b1

Coeficiente sin signo de 0 a 255.

Valor devuelto

Devuelve los 16 bits bajos de a0*b0+a1*b1.

Condiciones y límites

En FC, los nombres q8_8/q1_7 usan productos enteros sin el desplazamiento de escala de punto fijo. Las funciones Q1.7 son alias de sus operaciones con signo correspondientes. El código que llama gestiona las unidades; los resultados con signo se devuelven como patrones de bits u16.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Compara entradas literales con valores guardados en RAM. Los resultados representativos se muestran como valores de 16 bits sin signo; los negativos usan patrones en complemento a dos. GB ajusta la escala de los productos de punto fijo, mientras FC devuelve productos enteros, por lo que entradas idénticas pueden dar resultados distintos.

El fragmento guarda estos valores de 16 bits: result[22]=64959, result[23]=64959. Cada cálculo con entrada literal coincide con su equivalente con entrada en RAM. La pantalla muestra valores representativos; el array result guarda todos los resultados.

__dot2_q8_8: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:63

Notas de la declaración (original)
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

Calcula un producto escalar sin signo de tres términos.

Sintaxis

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

Parámetros

a0

Palabra sin signo de 0 a 65535.

a1

Palabra sin signo de 0 a 65535.

a2

Palabra sin signo de 0 a 65535.

b0

Coeficiente sin signo de 0 a 255.

b1

Coeficiente sin signo de 0 a 255.

b2

Coeficiente sin signo de 0 a 255.

Valor devuelto

Devuelve los 16 bits bajos de a0*b0+a1*b1+a2*b2.

Condiciones y límites

En FC, los nombres q8_8/q1_7 usan productos enteros sin el desplazamiento de escala de punto fijo. Las funciones Q1.7 son alias de sus operaciones con signo correspondientes. El código que llama gestiona las unidades; los resultados con signo se devuelven como patrones de bits u16.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Compara entradas literales con valores guardados en RAM. Los resultados representativos se muestran como valores de 16 bits sin signo; los negativos usan patrones en complemento a dos. GB ajusta la escala de los productos de punto fijo, mientras FC devuelve productos enteros, por lo que entradas idénticas pueden dar resultados distintos.

El fragmento guarda estos valores de 16 bits: result[24]=2498, result[25]=2498. Cada cálculo con entrada literal coincide con su equivalente con entrada en RAM. La pantalla muestra valores representativos; el array result guarda todos los resultados.

__dot3_q8_8: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:65

Notas de la declaración (original)
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 llama a la misma rutina del compilador que __pad_read1_d1. Lee D1 en $4016 con la misma secuencia de captura, resultado de ocho bits y efectos secundarios. El nombre expresa su uso para entradas de expansión; no activa otro modo de dispositivo ni combina D0 con D1.

Sintaxis

c
u8 __exp_pad_read1(void);

Valor devuelto

Máscara de botones mantenidos de 8 bits en orden NES. Cada bit a 1 identifica un botón pulsado; 0 significa ninguno. No es un booleano normalizado.

Condiciones y límites

Formato NES directo: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit a 1 significa pulsado. Usa estos valores con nes_pad_* y los resultados directos de __pad_*; no los confundas con las máscaras comunes BTN_* de input.h.

Usa un periférico que entregue una secuencia de mando serie de ocho bits compatible por la línea D1 elegida. La función no identifica el dispositivo ni implementa todos los protocolos de expansión. Cada llamada vuelve a fijar ambos flujos de control; termina una lectura completa antes de iniciar otra. Los resultados son lecturas nuevas y no actualizan las cachés input_* ni nes_pad1_*.

El modelo de mandos de KUROSAKI publicado solo devuelve D0 y el bit 6 fijo. Por ello, D1 y el bit del micrófono siempre son 0 en este emulador, incluso con botones normales pulsados. La ejecución incluida verifica la compilación, la entrada normal y esta observación de señal cero. No verifica señales periféricas distintas de cero, tiempos eléctricos ni compatibilidad real; un cero aquí no demuestra que el dispositivo funcione ni que estas funciones sean incapaces de leerlo en hardware.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Este programa compara la entrada normal D0 con D1 y el nivel digital del micrófono. Mantén A en el mando 1 y B en el mando 2 hasta capturar la lectura. NORMAL P1 y NORMAL P2 muestran 001 y 002. En la ejecución KUROSAKI incluida, D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL y MIC DIRECT muestran 000 porque esas líneas no se emulan. Después, los valores quedan fijos. Con periféricos adecuados y sus líneas implementadas, los valores dependen de la entrada real; interprétalos según los bits descritos arriba en lugar de esperar estos ceros.

El modelo de mandos de KUROSAKI publicado solo devuelve D0 y el bit 6 fijo. Por ello, D1 y el bit del micrófono siempre son 0 en este emulador, incluso con botones normales pulsados. La ejecución incluida verifica la compilación, la entrada normal y esta observación de señal cero. No verifica señales periféricas distintas de cero, tiempos eléctricos ni compatibilidad real; un cero aquí no demuestra que el dispositivo funcione ni que estas funciones sean incapaces de leerlo en hardware.

__exp_pad_read1: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:227

Notas de la declaración (original)
Alias the port-1 D1 controller reader; the attached device must use the expected serial protocol.
__exp_pad_read2

__exp_pad_read2 llama a la misma rutina del compilador que __pad_read2_d1. Lee D1 en $4017 con la misma secuencia de captura, resultado de ocho bits y efectos secundarios. El nombre expresa su uso para entradas de expansión; no activa otro modo de dispositivo ni combina D0 con D1.

Sintaxis

c
u8 __exp_pad_read2(void);

Valor devuelto

Máscara de botones mantenidos de 8 bits en orden NES. Cada bit a 1 identifica un botón pulsado; 0 significa ninguno. No es un booleano normalizado.

Condiciones y límites

Formato NES directo: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit a 1 significa pulsado. Usa estos valores con nes_pad_* y los resultados directos de __pad_*; no los confundas con las máscaras comunes BTN_* de input.h.

Usa un periférico que entregue una secuencia de mando serie de ocho bits compatible por la línea D1 elegida. La función no identifica el dispositivo ni implementa todos los protocolos de expansión. Cada llamada vuelve a fijar ambos flujos de control; termina una lectura completa antes de iniciar otra. Los resultados son lecturas nuevas y no actualizan las cachés input_* ni nes_pad1_*.

El modelo de mandos de KUROSAKI publicado solo devuelve D0 y el bit 6 fijo. Por ello, D1 y el bit del micrófono siempre son 0 en este emulador, incluso con botones normales pulsados. La ejecución incluida verifica la compilación, la entrada normal y esta observación de señal cero. No verifica señales periféricas distintas de cero, tiempos eléctricos ni compatibilidad real; un cero aquí no demuestra que el dispositivo funcione ni que estas funciones sean incapaces de leerlo en hardware.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Este programa compara la entrada normal D0 con D1 y el nivel digital del micrófono. Mantén A en el mando 1 y B en el mando 2 hasta capturar la lectura. NORMAL P1 y NORMAL P2 muestran 001 y 002. En la ejecución KUROSAKI incluida, D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL y MIC DIRECT muestran 000 porque esas líneas no se emulan. Después, los valores quedan fijos. Con periféricos adecuados y sus líneas implementadas, los valores dependen de la entrada real; interprétalos según los bits descritos arriba en lugar de esperar estos ceros.

El modelo de mandos de KUROSAKI publicado solo devuelve D0 y el bit 6 fijo. Por ello, D1 y el bit del micrófono siempre son 0 en este emulador, incluso con botones normales pulsados. La ejecución incluida verifica la compilación, la entrada normal y esta observación de señal cero. No verifica señales periféricas distintas de cero, tiempos eléctricos ni compatibilidad real; un cero aquí no demuestra que el dispositivo funcione ni que estas funciones sean incapaces de leerlo en hardware.

__exp_pad_read2: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:229

Notas de la declaración (original)
Alias the port-2 D1 controller reader; this is not an expansion-device discovery operation.
__far_memcpy

Selecciona temporalmente un banco de ROM, copia len bytes de src a dst en orden de direcciones creciente y restaura el banco guardado por el compilador. En esta plataforma, __farmemcpy y __far_memcpy comparten la implementación.

Sintaxis

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

Parámetros

dst

Destino en memoria escribible, con espacio para todos los bytes copiados. Salvo en una transferencia de cero bytes, no se comprueba si el destino es nulo o inválido.

bank

Selector de banco de 8 bits para la ROM de origen. Use un banco admitido por el cartucho y la distribución del compilador; se especifica por separado de la dirección.

src

Dirección CPU de 16 bits de los datos en el mapeo de ROM seleccionado. La declaración de FC recibe u16; convierta el puntero mediante (u16)pointer.

len

Cantidad de bytes, de 0 a 65535. Con cero no se copia memoria, pero sí se selecciona y restaura el banco.

Valor devuelto

Ninguno (void).

Condiciones y límites

Mantenga todo el origen en el mapeo de ROM previsto y todo el destino en memoria escribible. La copia no cambia automáticamente de banco al cruzar una ventana, no trata solapamientos como memmove ni sincroniza escrituras en VRAM. Para VRAM, use las API de transferencia gráfica.

Los ejemplos sitúan el código llamante en el banco fijo 0 para que cambiar el banco de origen no retire el código en ejecución. Las transferencias son síncronas. No las interrumpa con otra copia que cambie bancos ni suponga que la función garantiza por sí sola la seguridad de todas las interrupciones.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Copia dos patrones, 32 bytes, del banco 2 partiendo del banco 1. Compara todos los bytes, comprueba la restauración del banco 1 y verifica que una copia de longitud cero conserva el testigo 0x55.

Imagen esperada: una fila roja empieza en (16,32) y ocupa 72×8 píxeles. Repite tres veces un triángulo de 8×8, un cuadrado de 8×8 con borde de un píxel y un tile con sus cuatro columnas izquierdas rellenas. El triángulo crece desde un píxel arriba a la izquierda hasta ocho en la fila inferior.

En FC, la pareja azul en (16,64) muestra __far_memcpy; la verde en (16,96), asset_load_raw seguido de una carga en CHR. Cada pareja ocupa 16×8 píxeles sobre negro. Permiten ver los datos gráficos copiados, no solo valores numéricos de estado.

El estado esperado es FAILED CHECKS 000 y FIRST FAILURE 00. La verificación ejecuta 300 fotogramas y compara texto, formas y colores con lo esperado. Es una prueba en emulador, no en hardware físico.

__far_memcpy: SUROM512
SUROM512

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:86

Notas de la declaración (original)
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

Llama a una función con nombre y sin argumentos. El compilador FC usa su banco asignado para elegir una llamada directa o una rutina que restaura el banco. Evalúa el argumento bank, pero la ubicación de la función determina la selección.

Sintaxis

c
__farcall(bank, function_name);

Parámetros

bank

Número de banco ROM de 8 bits sin signo. En una llamada debe coincidir con la ubicación de la función; en una lectura selecciona el origen. Usa el intervalo válido para el cartucho.

func

Nombre declarado de una función sin parámetros. Esta intrínseca de llamada por nombre no acepta una variable que contenga una dirección de función. El argumento bank no se pasa al callback.

Valor devuelto

El tipo de resultado depende de la función llamada. Un callback void no devuelve valor; los callbacks u8 y u16 devuelven su valor escalar. El ejemplo mostrado usa callbacks void.

Condiciones y límites

Estas llamadas no pasan argumentos del usuario ni hacen que el callback admita reentrada por interrupciones. Gestiona correctamente sus datos, pila e interrupciones. La restauración de bancos se prueba con llamadas anidadas y repetidas; el ejemplo no cubre hardware físico ni cualquier posible intercalado de interrupciones.

Los bancos FC son números lógicos del compilador. Cero designa ROM común, no un destino de cambio; la rutina lo convierte en banco 1. SUROM512 admite 1–30: una constante fuera de rango pasada directamente a la intrínseca de cambio produce un error de compilación; un valor inválido en ejecución selecciona 1 y activa el estado de fallo del mapper. Lee la ROM común directamente, sin pedir un cambio al banco cero.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

En este punto del programa completo, __farcall eleva callback_count a 2. El callback lee D3 del banco 3. Una lectura cercana posterior de B2 en home2 verifica el retorno con el banco 2 seleccionado.

El programa completo enseña a identificar datos con un banco y una dirección cercana, a llamar callbacks entre bancos y a distinguir el registro de la biblioteca del mapa real. Todos los números se muestran en hexadecimal. BANK IN y BANK OUT deben ser 02; BYTE HEX, D3; WORD HEX, 5C7A; FAILED CHECKS y FIRST FAILURE, 00. CGB y FC muestran texto azul; DMG, negro.

CALL COUNT muestra 02: la macro far_call y __farcall invocan el callback una vez cada uno.

__farcall: MMC3
MMC3

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:83

Notas de la declaración (original)
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 es otro nombre de la función intrínseca de FC __far_memcpy. Recibe los mismos argumentos y usa la misma rutina de copia y restauración del banco; elegir este nombre no cambia el método ni la velocidad de transferencia. El ejemplo lo utiliza para leer patrones de tiles de la ROM y copiarlos a la RAM.

Selecciona temporalmente un banco de ROM, copia len bytes de src a dst en orden de direcciones creciente y restaura el banco guardado por el compilador. En esta plataforma, __farmemcpy y __far_memcpy comparten la implementación.

Sintaxis

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

Parámetros

dst

Destino en memoria escribible, con espacio para todos los bytes copiados. Salvo en una transferencia de cero bytes, no se comprueba si el destino es nulo o inválido.

bank

Selector de banco de 8 bits para la ROM de origen. Use un banco admitido por el cartucho y la distribución del compilador; se especifica por separado de la dirección.

src

Dirección CPU de 16 bits de los datos en el mapeo de ROM seleccionado. La declaración de FC recibe u16; convierta el puntero mediante (u16)pointer.

len

Cantidad de bytes, de 0 a 65535. Con cero no se copia memoria, pero sí se selecciona y restaura el banco.

Valor devuelto

Ninguno (void).

Condiciones y límites

El compilador registra este nombre directamente, por lo que se puede llamar sin una declaración adicional en intrinsics.h. Los argumentos son, por este orden, un puntero a RAM, un número de banco de 8 bits, una dirección de CPU de 16 bits y una cantidad de bytes de 16 bits. src no es un desplazamiento desde el principio del archivo ROM.

Mantenga todo el origen en el mapeo de ROM previsto y todo el destino en memoria escribible. La copia no cambia automáticamente de banco al cruzar una ventana, no trata solapamientos como memmove ni sincroniza escrituras en VRAM. Para VRAM, use las API de transferencia gráfica.

Los ejemplos sitúan el código llamante en el banco fijo 0 para que cambiar el banco de origen no retire el código en ejecución. Las transferencias son síncronas. No las interrumpa con otra copia que cambie bancos ni suponga que la función garantiza por sí sola la seguridad de todas las interrupciones.

Si len es cero, no se modifica ningún byte de destino, pero sí se selecciona y restaura el banco. La función no devuelve ningún valor.

__far_memcpy

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Copia dos patrones, 32 bytes, del banco 2 partiendo del banco 1. Compara todos los bytes, comprueba la restauración del banco 1 y verifica que una copia de longitud cero conserva el testigo 0x55.

Imagen esperada: una fila roja empieza en (16,32) y ocupa 72×8 píxeles. Repite tres veces un triángulo de 8×8, un cuadrado de 8×8 con borde de un píxel y un tile con sus cuatro columnas izquierdas rellenas. El triángulo crece desde un píxel arriba a la izquierda hasta ocho en la fila inferior.

El triángulo y el contorno cuadrado azules en el píxel (16,64) muestran los bytes copiados por __farmemcpy. La pareja verde de abajo, en (16,96), procede de asset_load_raw. Cada pareja ocupa 16×8 píxeles sobre fondo negro; los bytes copiados se transfieren a CHR RAM para mostrarlos.

El estado esperado es FAILED CHECKS 000 y FIRST FAILURE 00. La verificación ejecuta 300 fotogramas y compara texto, formas y colores con lo esperado. Es una prueba en emulador, no en hardware físico.

__farmemcpy: SUROM512 — El triángulo y el contorno cuadrado azules en el píxel (16,64) muestran los bytes copiados por __farmemcpy. La pareja verde de abajo, en (16,96), procede de asset_load_raw. Cada pareja ocupa 16×8 píxeles sobre fondo negro; los bytes copiados se transfieren a CHR RAM para mostrarlos.
SUROM512 — El triángulo y el contorno cuadrado azules en el píxel (16,64) muestran los bytes copiados por __farmemcpy. La pareja verde de abajo, en (16,96), procede de asset_load_raw. Cada pareja ocupa 16×8 píxeles sobre fondo negro; los bytes copiados se transfieren a CHR RAM para mostrarlos.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

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

Lee un valor de 16 bits little-endian en addr y addr+1 con una selección temporal de banco, y después lo restaura. Es la intrínseca utilizada por far_data_read16.

Sintaxis

c
u16 __farpeek16(u8 bank, u16 addr);

Parámetros

bank

Número de banco ROM de 8 bits sin signo. En una llamada debe coincidir con la ubicación de la función; en una lectura selecciona el origen. Usa el intervalo válido para el cartucho.

addr

Dirección CPU de 16 bits de los datos en el mapeo de ROM seleccionado. La declaración de FC recibe u16; convierta el puntero mediante (u16)pointer.

Valor devuelto

La palabra little-endian leída, como u16 (0–65535).

Condiciones y límites

El byte o la palabra completos deben ser legibles en el mapa CPU seleccionado. No se validan punteros, no se recortan direcciones ni se cambia automáticamente de banco al cruzar una ventana. Una palabra necesita dos bytes consecutivos válidos.

Las lecturas y copias lejanas son síncronas y usan memoria de trabajo compartida del compilador. No dejes que una interrupción ejecute otra transferencia sobre esa memoria mientras está en uso. En GB, la copia también guarda el banco anterior en una posición HRAM compartida. El ejemplo ejecuta las operaciones desde código fijo, sin un manejador de transferencia que compita.

Los bancos FC son números lógicos del compilador. Cero designa ROM común, no un destino de cambio; la rutina lo convierte en banco 1. SUROM512 admite 1–30: una constante fuera de rango pasada directamente a la intrínseca de cambio produce un error de compilación; un valor inválido en ejecución selecciona 1 y activa el estado de fallo del mapper. Lee la ROM común directamente, sin pedir un cambio al banco cero.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Este fragmento usa __farpeek16 para leer payload+1 del banco 3 y comprueba 5C7A. El programa completo también lee home2 con un puntero cercano para confirmar que se ha restaurado el banco 2.

El programa completo enseña a identificar datos con un banco y una dirección cercana, a llamar callbacks entre bancos y a distinguir el registro de la biblioteca del mapa real. Todos los números se muestran en hexadecimal. BANK IN y BANK OUT deben ser 02; BYTE HEX, D3; WORD HEX, 5C7A; FAILED CHECKS y FIRST FAILURE, 00. CGB y FC muestran texto azul; DMG, negro.

CALL COUNT muestra 02: la macro far_call y __farcall invocan el callback una vez cada uno.

__farpeek16: MMC3
MMC3

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:90

Notas de la declaración (original)
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

Selecciona temporalmente el banco indicado, lee un byte en la dirección CPU y restaura el banco anterior. Es la intrínseca utilizada por far_data_read8.

Sintaxis

c
u8 __farpeek8(u8 bank, u16 addr);

Parámetros

bank

Número de banco ROM de 8 bits sin signo. En una llamada debe coincidir con la ubicación de la función; en una lectura selecciona el origen. Usa el intervalo válido para el cartucho.

addr

Dirección CPU de 16 bits de los datos en el mapeo de ROM seleccionado. La declaración de FC recibe u16; convierta el puntero mediante (u16)pointer.

Valor devuelto

El byte leído, como u8 (0–255).

Condiciones y límites

El byte o la palabra completos deben ser legibles en el mapa CPU seleccionado. No se validan punteros, no se recortan direcciones ni se cambia automáticamente de banco al cruzar una ventana. Una palabra necesita dos bytes consecutivos válidos.

Las lecturas y copias lejanas son síncronas y usan memoria de trabajo compartida del compilador. No dejes que una interrupción ejecute otra transferencia sobre esa memoria mientras está en uso. En GB, la copia también guarda el banco anterior en una posición HRAM compartida. El ejemplo ejecuta las operaciones desde código fijo, sin un manejador de transferencia que compita.

Los bancos FC son números lógicos del compilador. Cero designa ROM común, no un destino de cambio; la rutina lo convierte en banco 1. SUROM512 admite 1–30: una constante fuera de rango pasada directamente a la intrínseca de cambio produce un error de compilación; un valor inválido en ejecución selecciona 1 y activa el estado de fallo del mapper. Lee la ROM común directamente, sin pedir un cambio al banco cero.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Este fragmento usa __farpeek8 para leer payload del banco 3 y comprueba D3. El programa completo también lee home2 con un puntero cercano para confirmar que se ha restaurado el banco 2.

El programa completo enseña a identificar datos con un banco y una dirección cercana, a llamar callbacks entre bancos y a distinguir el registro de la biblioteca del mapa real. Todos los números se muestran en hexadecimal. BANK IN y BANK OUT deben ser 02; BYTE HEX, D3; WORD HEX, 5C7A; FAILED CHECKS y FIRST FAILURE, 00. CGB y FC muestran texto azul; DMG, negro.

CALL COUNT muestra 02: la macro far_call y __farcall invocan el callback una vez cada uno.

__farpeek8: MMC3
MMC3

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:88

Notas de la declaración (original)
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

Comprueba el patrón de detección del teclado antes de interpretar las pulsaciones.

Sintaxis

c
u8 __fkb_detect(void);

Valor devuelto

Devuelve 1 si la fila 9, columna 0 se lee como 0x1E y la matriz desactivada se lee como cero; en caso contrario, devuelve 0. Es una detección heurística basada en el patrón de señales, no un código de identificación del dispositivo.

Condiciones y límites

Estas API acceden directamente a $4016/$4017. No intercales operaciones de mando, teclado o serie mediante interrupciones en los mismos puertos. También pueden afectar a las posiciones de desplazamiento del controlador. El ejemplo deshabilita NMI mientras captura entradas y la habilita al inicializar la pantalla.

Escribe 5 en $4016 para volver a la fila 0 y avanza filas con transiciones de alto a bajo del bit de selección de columna. Además de las instrucciones circundantes, el código generado incluye 12 ciclos CPU de NOP después del reinicio y 50 tras seleccionar columna. Al salir escribe cero para deshabilitar la matriz.

Otra prueba de regresión comprueba filas 0..9, ambas columnas, el barrido de nueve filas, los retardos de estabilización y el estado final del puerto mediante trazas de bus CPU ejecutadas. No inyecta señales de teclas físicas.

La fila 9 no contiene teclas. Si una condición no se cumple, se devuelve cero de inmediato. KUROSAKI devuelve cero en la primera lectura, por lo que la detección falla en ese entorno.

Estas imágenes ejecutan las ROM descargables sin cambios en KUROSAKI. Su bus de entrada modela mandos, pero no señales de teclado, pistola de luz ni adaptador serie. Los bits externos se leen como cero, por lo que las capturas no verifican esos dispositivos. No se han probado teclas físicas, detección óptica ni comunicación.

NESdev: teclado Family BASIC

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

En esta ejecución de KUROSAKI, DEVICE DETECT, ROW 6 COL 0 y A KEY - GUARDED valen 000; las nueve parejas COL0/COL1 son 00. A es fila 6, columna 0, bit 4, pero al fallar la detección no se informa del cero bruto como tecla pulsada. La tabla es una captura única al arrancar.

__fkb_detect: KEYBOARD-DEFAULT — En esta ejecución de KUROSAKI, DEVICE DETECT, ROW 6 COL 0 y A KEY - GUARDED valen 000; las nueve parejas COL0/COL1 son 00. A es fila 6, columna 0, bit 4, pero al fallar la detección no se informa del cero bruto como tecla pulsada. La tabla es una captura única al arrancar.
KEYBOARD-DEFAULT — En esta ejecución de KUROSAKI, DEVICE DETECT, ROW 6 COL 0 y A KEY - GUARDED valen 000; las nueve parejas COL0/COL1 son 00. A es fila 6, columna 0, bit 4, pero al fallar la detección no se informa del cero bruto como tecla pulsada. La tabla es una captura única al arrancar.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:259

Notas de la declaración (original)
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

Lee una fila y un grupo de columnas concretos sin un búfer de 18 bytes cuando solo necesites ese grupo.

Sintaxis

c
u8 __fkb_read_row_col(u8 row, u8 col);

Parámetros

row

Usa 0..8 para las filas de teclas o 9 para la fila de detección sin teclas. No se genera una comprobación de rango en tiempo de ejecución.

col

0 o 1. Solo se usa el bit menos significativo: los valores pares seleccionan el grupo 0 y los impares, el grupo 1.

Valor devuelto

Devuelve $4017 & 0x1E sin desplazar los bits hacia las posiciones menos significativas.

Condiciones y límites

Estas API acceden directamente a $4016/$4017. No intercales operaciones de mando, teclado o serie mediante interrupciones en los mismos puertos. También pueden afectar a las posiciones de desplazamiento del controlador. El ejemplo deshabilita NMI mientras captura entradas y la habilita al inicializar la pantalla.

Los grupos de teclas son los bits sin procesar 1..4: pulsar borra su bit y un grupo en reposo vale 0x1E. No son texto ASCII ni un mapa donde uno signifique pulsado. Un dispositivo desconectado también puede leerse como cero; no lo interpretes como todas las teclas pulsadas sin comprobar su presencia.

Escribe 5 en $4016 para volver a la fila 0 y avanza filas con transiciones de alto a bajo del bit de selección de columna. Además de las instrucciones circundantes, el código generado incluye 12 ciclos CPU de NOP después del reinicio y 50 tras seleccionar columna. Al salir escribe cero para deshabilitar la matriz.

Otra prueba de regresión comprueba filas 0..9, ambas columnas, el barrido de nueve filas, los retardos de estabilización y el estado final del puerto mediante trazas de bus CPU ejecutadas. No inyecta señales de teclas físicas.

Vuelve a la fila 0 y avanza de nuevo en cada llamada; no reutiliza la posición de fila de la llamada anterior.

Estas imágenes ejecutan las ROM descargables sin cambios en KUROSAKI. Su bus de entrada modela mandos, pero no señales de teclado, pistola de luz ni adaptador serie. Los bits externos se leen como cero, por lo que las capturas no verifican esos dispositivos. No se han probado teclas físicas, detección óptica ni comunicación.

NESdev: teclado Family BASIC

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

En esta ejecución de KUROSAKI, DEVICE DETECT, ROW 6 COL 0 y A KEY - GUARDED valen 000; las nueve parejas COL0/COL1 son 00. A es fila 6, columna 0, bit 4, pero al fallar la detección no se informa del cero bruto como tecla pulsada. La tabla es una captura única al arrancar.

__fkb_read_row_col: KEYBOARD-DEFAULT — En esta ejecución de KUROSAKI, DEVICE DETECT, ROW 6 COL 0 y A KEY - GUARDED valen 000; las nueve parejas COL0/COL1 son 00. A es fila 6, columna 0, bit 4, pero al fallar la detección no se informa del cero bruto como tecla pulsada. La tabla es una captura única al arrancar.
KEYBOARD-DEFAULT — En esta ejecución de KUROSAKI, DEVICE DETECT, ROW 6 COL 0 y A KEY - GUARDED valen 000; las nueve parejas COL0/COL1 son 00. A es fila 6, columna 0, bit 4, pero al fallar la detección no se informa del cero bruto como tecla pulsada. La tabla es una captura única al arrancar.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:264

Notas de la declaración (original)
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

Escanea las nueve filas y los dos grupos de columnas en 18 bytes para consultar varias teclas a partir de una sola captura de la matriz.

Sintaxis

c
void __fkb_scan(u8* dst18);

Parámetros

dst18

Espacio de escritura de al menos 18 bytes. El elemento row*2+col recibe el valor sin procesar de ese grupo. No se comprueba si el puntero es nulo ni si la longitud es suficiente.

Valor devuelto

No devuelve ningún valor.

Condiciones y límites

Estas API acceden directamente a $4016/$4017. No intercales operaciones de mando, teclado o serie mediante interrupciones en los mismos puertos. También pueden afectar a las posiciones de desplazamiento del controlador. El ejemplo deshabilita NMI mientras captura entradas y la habilita al inicializar la pantalla.

Los grupos de teclas son los bits sin procesar 1..4: pulsar borra su bit y un grupo en reposo vale 0x1E. No son texto ASCII ni un mapa donde uno signifique pulsado. Un dispositivo desconectado también puede leerse como cero; no lo interpretes como todas las teclas pulsadas sin comprobar su presencia.

Escribe 5 en $4016 para volver a la fila 0 y avanza filas con transiciones de alto a bajo del bit de selección de columna. Además de las instrucciones circundantes, el código generado incluye 12 ciclos CPU de NOP después del reinicio y 50 tras seleccionar columna. Al salir escribe cero para deshabilitar la matriz.

Otra prueba de regresión comprueba filas 0..9, ambas columnas, el barrido de nueve filas, los retardos de estabilización y el estado final del puerto mediante trazas de bus CPU ejecutadas. No inyecta señales de teclas físicas.

Lee primero la columna 0 y después la 1 de cada fila. Es un barrido secuencial, no una captura simultánea de todas las teclas. Un grupo con valor cero no termina el barrido antes de tiempo.

Estas imágenes ejecutan las ROM descargables sin cambios en KUROSAKI. Su bus de entrada modela mandos, pero no señales de teclado, pistola de luz ni adaptador serie. Los bits externos se leen como cero, por lo que las capturas no verifican esos dispositivos. No se han probado teclas físicas, detección óptica ni comunicación.

NESdev: teclado Family BASIC

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

En esta ejecución de KUROSAKI, DEVICE DETECT, ROW 6 COL 0 y A KEY - GUARDED valen 000; las nueve parejas COL0/COL1 son 00. A es fila 6, columna 0, bit 4, pero al fallar la detección no se informa del cero bruto como tecla pulsada. La tabla es una captura única al arrancar.

__fkb_scan: KEYBOARD-DEFAULT — En esta ejecución de KUROSAKI, DEVICE DETECT, ROW 6 COL 0 y A KEY - GUARDED valen 000; las nueve parejas COL0/COL1 son 00. A es fila 6, columna 0, bit 4, pero al fallar la detección no se informa del cero bruto como tecla pulsada. La tabla es una captura única al arrancar.
KEYBOARD-DEFAULT — En esta ejecución de KUROSAKI, DEVICE DETECT, ROW 6 COL 0 y A KEY - GUARDED valen 000; las nueve parejas COL0/COL1 son 00. A es fila 6, columna 0, bit 4, pero al fallar la detección no se informa del cero bruto como tecla pulsada. La tabla es una captura única al arrancar.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:261

Notas de la declaración (original)
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

Emite SEI para poner a 1 el bit I del registro de estado de la CPU y bloquear las solicitudes IRQ ordinarias. Úselo en una sección crítica frente a IRQ. No desactiva NMI, silencia la APU, atiende la causa de una IRQ ni guarda la máscara anterior.

Sintaxis

c
void __irq_disable(void);

Valor devuelto

Ninguno (void).

Condiciones y límites

El bit I solo controla IRQ ordinarias: NMI puede interrumpir esta sección. Bloquear IRQ no libera una solicitud pendiente del dispositivo ni protege datos compartidos con NMI. Prepare las fuentes y los manejadores por separado. El ejemplo no prueba periféricos que generen IRQ.

NESdev: NMI

NESdev: interrupciones de CPU

Ejemplo de uso

c
__irq_disable();
disabled=(__irq_save()&4)>>2; // The CPU I bit must be 1.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa distingue la máscara IRQ de la CPU del control NMI de la PPU. En azul sobre negro deben aparecer IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 y RESTORED I = 000. Cada número es el bit I extraído, no el byte de estado completo. FAILED CHECKS debe ser 000.

Ejecute al menos 360 fotogramas para completar la demostración de 256 esperas. NMI DELTA 3 WAITS debe ser 003 incluso con IRQ bloqueada; NMI DELTA 256 WAITS debe ser 000 por el retorno cíclico del byte. NMI OFF DELTA también debe ser 000. El programa usa el manejador predeterminado sin runtime.c. Hay 21 comprobaciones de estado adicionales, incluida la restauración de indicadores y esperas observadas durante un tiempo acotado con NMI desactivada o un manejador propio vacío. Se verifica KUROSAKI/NROM, no hardware real.

__irq_disable: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:176

Notas de la declaración (original)
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

Emite CLI para borrar el bit I del registro de estado de la CPU y permitir la atención de IRQ ordinarias. No configura una fuente de interrupción del mapper o de la APU. Prepare el manejador y la lógica que libera la solicitud antes de activar esa fuente.

Sintaxis

c
void __irq_enable(void);

Valor devuelto

Ninguno (void).

Condiciones y límites

El bit I solo controla IRQ ordinarias: NMI puede interrumpir esta sección. Bloquear IRQ no libera una solicitud pendiente del dispositivo ni protege datos compartidos con NMI. Prepare las fuentes y los manejadores por separado. El ejemplo no prueba periféricos que generen IRQ.

NESdev: NMI

NESdev: interrupciones de CPU

Ejemplo de uso

c
__irq_enable();
enabled=(__irq_save()&4)>>2; // Save returns the old I bit: 0, then masks IRQ.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa distingue la máscara IRQ de la CPU del control NMI de la PPU. En azul sobre negro deben aparecer IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 y RESTORED I = 000. Cada número es el bit I extraído, no el byte de estado completo. FAILED CHECKS debe ser 000.

Ejecute al menos 360 fotogramas para completar la demostración de 256 esperas. NMI DELTA 3 WAITS debe ser 003 incluso con IRQ bloqueada; NMI DELTA 256 WAITS debe ser 000 por el retorno cíclico del byte. NMI OFF DELTA también debe ser 000. El programa usa el manejador predeterminado sin runtime.c. Hay 21 comprobaciones de estado adicionales, incluida la restauración de indicadores y esperas observadas durante un tiempo acotado con NMI desactivada o un manejador propio vacío. Se verifica KUROSAKI/NROM, no hardware real.

__irq_enable: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:178

Notas de la declaración (original)
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

Evalúa el byte de estado recibido, lo apila con PHA y restaura el estado de la CPU con PLP. Recupera la máscara IRQ y los indicadores aritméticos guardados; no activa IRQ de forma incondicional. Las expresiones C posteriores pueden volver a modificar los indicadores aritméticos.

Sintaxis

c
void __irq_restore(u8 state);

Parámetros

state

El estado completo de la CPU de 8 bits capturado por __irq_save. Consérvelo para __irq_restore; no lo convierta en un booleano. El bit 2 es I: 1 bloquea IRQ y 0 la permite. La representación apilada por PHP pone a 1 los bits 4 y 5.

Valor devuelto

Ninguno (void).

Condiciones y límites

El bit I solo controla IRQ ordinarias: NMI puede interrumpir esta sección. Bloquear IRQ no libera una solicitud pendiente del dispositivo ni protege datos compartidos con NMI. Prepare las fuentes y los manejadores por separado. El ejemplo no prueba periféricos que generen IRQ.

NESdev: NMI

NESdev: interrupciones de CPU

Ejemplo de uso

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.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa distingue la máscara IRQ de la CPU del control NMI de la PPU. En azul sobre negro deben aparecer IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 y RESTORED I = 000. Cada número es el bit I extraído, no el byte de estado completo. FAILED CHECKS debe ser 000.

Ejecute al menos 360 fotogramas para completar la demostración de 256 esperas. NMI DELTA 3 WAITS debe ser 003 incluso con IRQ bloqueada; NMI DELTA 256 WAITS debe ser 000 por el retorno cíclico del byte. NMI OFF DELTA también debe ser 000. El programa usa el manejador predeterminado sin runtime.c. Hay 21 comprobaciones de estado adicionales, incluida la restauración de indicadores y esperas observadas durante un tiempo acotado con NMI desactivada o un manejador propio vacío. Se verifica KUROSAKI/NROM, no hardware real.

__irq_restore: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:182

Notas de la declaración (original)
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

Guarda el byte de estado de la CPU con PHP, bloquea IRQ con SEI y devuelve el byte guardado mediante PLA. El bit I guardado refleja el estado anterior al bloqueo. Combínelo con __irq_restore para que una sección crítica anidada conserve una máscara IRQ que ya estaba desactivada.

Sintaxis

c
u8 __irq_save(void);

Valor devuelto

El estado completo de la CPU de 8 bits capturado por __irq_save. Consérvelo para __irq_restore; no lo convierta en un booleano. El bit 2 es I: 1 bloquea IRQ y 0 la permite. La representación apilada por PHP pone a 1 los bits 4 y 5.

Condiciones y límites

El bit I solo controla IRQ ordinarias: NMI puede interrumpir esta sección. Bloquear IRQ no libera una solicitud pendiente del dispositivo ni protege datos compartidos con NMI. Prepare las fuentes y los manejadores por separado. El ejemplo no prueba periféricos que generen IRQ.

NESdev: NMI

NESdev: interrupciones de CPU

Ejemplo de uso

c
saved_state=__irq_save(); // Save all processor-status bits, then set I.
inside=(__irq_save()&4)>>2; // IRQ remains masked inside this section.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa distingue la máscara IRQ de la CPU del control NMI de la PPU. En azul sobre negro deben aparecer IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 y RESTORED I = 000. Cada número es el bit I extraído, no el byte de estado completo. FAILED CHECKS debe ser 000.

Ejecute al menos 360 fotogramas para completar la demostración de 256 esperas. NMI DELTA 3 WAITS debe ser 003 incluso con IRQ bloqueada; NMI DELTA 256 WAITS debe ser 000 por el retorno cíclico del byte. NMI OFF DELTA también debe ser 000. El programa usa el manejador predeterminado sin runtime.c. Hay 21 comprobaciones de estado adicionales, incluida la restauración de indicadores y esperas observadas durante un tiempo acotado con NMI desactivada o un manejador propio vacío. Se verifica KUROSAKI/NROM, no hardware real.

__irq_save: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:180

Notas de la declaración (original)
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 la operación de carga/recarga MMC3 de __mapper_irq_set. La palabra scanline no garantiza una fila absoluta de pantalla.

Sintaxis

c
void __irq_scanline_set(u8 line);

Parámetros

line

Valor de carga del contador de ocho bits, de 0 a 255, no una coordenada Y absoluta. El momento de recarga y las transiciones válidas de PPU A12 determinan cuándo se activa. Cero es válido y no significa «deshabilitar interrupciones».

Valor devuelto

No devuelve ningún valor.

Condiciones y límites

Las declaraciones de las funciones intrínsecas están en intrinsics.h. Incluye nes_game.h para las macros nes_mapper_irq_*. Selecciona la placa al compilar con --mapper.

Solo MMC3. Se necesitan un manejador __nes_irq, transiciones válidas de PPU A12, la habilitación IRQ del mapper y __irq_enable() de CPU. El ejemplo activa el renderizado con patrones de fondo en $0000 y de sprites en $1000. Esta lección no verifica la sincronización con una línea exacta en hardware físico.

Tanto el reconocimiento como la deshabilitación MMC3 escriben en $E000. Esto borra la petición actual y deshabilita las siguientes. Vuelve a llamar a __mapper_irq_enable() cuando corresponda para preparar otra interrupción. Ninguna operación cambia la máscara de interrupciones de CPU ni la habilitación NMI.

El compilador emite RTI para un manejador de interrupciones propio, pero este debe preservar los registros A/X/Y y temporales compartidos que modifique. El ejemplo guarda A/X/Y y solo usa un incremento de byte y un reconocimiento sin argumentos. Si añades llamadas C, revisa también el almacenamiento compartido que utilizan.

Ejemplo de uso

c
irq_phase=2;
    __irq_scanline_set(7); __mapper_irq_enable();
    for (i=0;i<6;i++) { m_wait(); }
    mapper_result[17]=irq_count;

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo MMC3 muestra marcadores CHR 19/20, 21/22 y 23/24. Las lecturas CIRAM dan A B A B con mirroring vertical y A A B B con horizontal. Los recuentos IRQ son 01→02→03→03: cada una de las tres primeras habilitaciones explícitas permite una interrupción; tras deshabilitar, el recuento no cambia. Cada etapa espera seis fotogramas.

__irq_scanline_set: MMC3-DEFAULT
MMC3-DEFAULT

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:324

Notas de la declaración (original)
Write the MMC3 IRQ latch and reload registers. This does not enable IRQ delivery.
__joypad2p_voice

__joypad2p_voice es otro nombre de entrada a la rutina __mic_read2p. Ambas leen el mismo bit digital del micrófono. “Voice” no implica procesamiento de audio: cada llamada toma una muestra distinta y los valores pueden diferir si la señal cambia entre ellas.

Lee la señal digital del micrófono del segundo mando de Famicom desde el bit 2 de $4016. La rutina lee el registro una vez, aplica la máscara 0x04 y normaliza a 0 o 1 sin invertir. No fija los estados de los mandos, graba audio, mide volumen ni reconoce voz.

Sintaxis

c
u8 __joypad2p_voice(void);

Valor devuelto

Exactamente 1 si $4016 & 0x04 no es cero; en caso contrario, 0. Es el nivel digital muestreado, no una máscara de botones, amplitud, frecuencia ni un recuento de palabras.

Condiciones y límites

Aunque el micrófono pertenece al mando 2, esta rutina accede a $4016, no a $4017. Una lectura del registro también puede avanzar la secuencia serie del mando cuando el estrobo está bajo; no intercales lecturas del micrófono en otro bucle de lectura de ocho bits. No se actualiza ninguna caché de historial de entrada.

El modelo de mandos de KUROSAKI publicado solo devuelve D0 y el bit 6 fijo. Por ello, D1 y el bit del micrófono siempre son 0 en este emulador, incluso con botones normales pulsados. La ejecución incluida verifica la compilación, la entrada normal y esta observación de señal cero. No verifica señales periféricas distintas de cero, tiempos eléctricos ni compatibilidad real; un cero aquí no demuestra que el dispositivo funcione ni que estas funciones sean incapaces de leerlo en hardware.

Ejemplo de uso

c
voice=__joypad2p_voice();

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Este programa compara la entrada normal D0 con D1 y el nivel digital del micrófono. Mantén A en el mando 1 y B en el mando 2 hasta capturar la lectura. NORMAL P1 y NORMAL P2 muestran 001 y 002. En la ejecución KUROSAKI incluida, D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL y MIC DIRECT muestran 000 porque esas líneas no se emulan. Después, los valores quedan fijos. Con periféricos adecuados y sus líneas implementadas, los valores dependen de la entrada real; interprétalos según los bits descritos arriba en lugar de esperar estos ceros.

El modelo de mandos de KUROSAKI publicado solo devuelve D0 y el bit 6 fijo. Por ello, D1 y el bit del micrófono siempre son 0 en este emulador, incluso con botones normales pulsados. La ejecución incluida verifica la compilación, la entrada normal y esta observación de señal cero. No verifica señales periféricas distintas de cero, tiempos eléctricos ni compatibilidad real; un cero aquí no demuestra que el dispositivo funcione ni que estas funciones sean incapaces de leerlo en hardware.

__joypad2p_voice: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:231

Notas de la declaración (original)
Return controller-port 4016 bit 2 normalized to 0 or 1; this is a microphone level bit, not PCM audio.
__mac16

Calcula una multiplicación-acumulación sin signo.

Sintaxis

c
u16 __mac16(u16 acc, u16 a, u8 b);

Parámetros

acc

Acumulador de 16 bits que se va a sumar.

a

Palabra sin signo de 0 a 65535.

b

Coeficiente sin signo de 0 a 255.

Valor devuelto

Devuelve los 16 bits bajos de acc+a*b.

Condiciones y límites

En FC, los nombres q8_8/q1_7 usan productos enteros sin el desplazamiento de escala de punto fijo. Las funciones Q1.7 son alias de sus operaciones con signo correspondientes. El código que llama gestiona las unidades; los resultados con signo se devuelven como patrones de bits u16.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Compara entradas literales con valores guardados en RAM. Los resultados representativos se muestran como valores de 16 bits sin signo; los negativos usan patrones en complemento a dos. GB ajusta la escala de los productos de punto fijo, mientras FC devuelve productos enteros, por lo que entradas idénticas pueden dar resultados distintos.

El fragmento guarda estos valores de 16 bits: result[8]=65115, result[9]=65115. Cada cálculo con entrada literal coincide con su equivalente con entrada en RAM. La pantalla muestra valores representativos; el array result guarda todos los resultados.

__mac16: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:55

Notas de la declaración (original)
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

Suma las diferencias absolutas de X e Y entre dos puntos.

Sintaxis

c
u8 __manhattan(u8 x1, u8 y1, u8 x2, u8 y2);

Parámetros

x1

Coordenada X del primer punto, de 0 a 255.

y1

Coordenada Y del primer punto, de 0 a 255.

x2

Coordenada X del segundo punto, de 0 a 255.

y2

Coordenada Y del segundo punto, de 0 a 255.

Valor devuelto

Devuelve el byte bajo de |x1−x2|+|y1−y2|; las distancias superiores a 255 se desbordan.

Ejemplo de uso

c
result[5]=__manhattan(2,3,9,11);result[6]=__manhattan(0,0,255,255);

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Prueba el interior, los límites, un punto exterior y ancho cero: INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1. Verifica la distancia 15, la distancia desbordada 254 y los índices de array 64/65280.

__manhattan: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:33

Notas de la declaración (original)
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

Convierte las coordenadas de una celda en un índice de array por filas.

Sintaxis

c
u16 __map_index(u8 x, u8 y, u8 width);

Parámetros

x

Columna de 0 a 255, normalmente menor que width.

y

Fila de 0 a 255.

width

Celdas por fila, de 0 a 255.

Valor devuelto

Devuelve y*width+x como u16; no valida las coordenadas.

Ejemplo de uso

c
result[7]=__map_index(4,3,20);result[8]=__map_index(255,255,255);

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Prueba el interior, los límites, un punto exterior y ancho cero: INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1. Verifica la distancia 15, la distancia desbordada 254 y los índices de array 64/65280.

__map_index: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:35

Notas de la declaración (original)
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

Devuelve el número de mapper elegido al compilar, para inicialización o visualización específica de la placa.

Sintaxis

c
u8 __mapper_id(void);

Valor devuelto

Número de mapper de ocho bits: NROM=0, MMC1=1, UxROM=2, CNROM=3, MMC3=4, MMC5=5, AxROM=7, VRC6=24, FME7=69 y VRC7=85. El compilador emite una constante; no sondea el hardware en ejecución.

Condiciones y límites

Las declaraciones de las funciones intrínsecas están en intrinsics.h. Incluye nes_game.h para las macros nes_mapper_irq_*. Selecciona la placa al compilar con --mapper.

Ejemplo de uso

c
mapper_result[0] = __mapper_id();

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo MMC3 muestra marcadores CHR 19/20, 21/22 y 23/24. Las lecturas CIRAM dan A B A B con mirroring vertical y A A B B con horizontal. Los recuentos IRQ son 01→02→03→03: cada una de las tres primeras habilitaciones explícitas permite una interrupción; tras deshabilitar, el recuento no cambia. Cada etapa espera seis fotogramas.

__mapper_id: MMC3-DEFAULT
MMC3-DEFAULT

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:314

Notas de la declaración (original)
Mapper
Return the low byte of the mapper number selected for this build; no cartridge probe is performed.
__mapper_irq_ack

Reconoce MMC3 en el manejador IRQ. Como escribe en $E000, también deshabilita las peticiones IRQ posteriores.

Sintaxis

c
void __mapper_irq_ack(void);

Valor devuelto

No devuelve ningún valor.

Condiciones y límites

Las declaraciones de las funciones intrínsecas están en intrinsics.h. Incluye nes_game.h para las macros nes_mapper_irq_*. Selecciona la placa al compilar con --mapper.

Solo MMC3. Se necesitan un manejador __nes_irq, transiciones válidas de PPU A12, la habilitación IRQ del mapper y __irq_enable() de CPU. El ejemplo activa el renderizado con patrones de fondo en $0000 y de sprites en $1000. Esta lección no verifica la sincronización con una línea exacta en hardware físico.

Tanto el reconocimiento como la deshabilitación MMC3 escriben en $E000. Esto borra la petición actual y deshabilita las siguientes. Vuelve a llamar a __mapper_irq_enable() cuando corresponda para preparar otra interrupción. Ninguna operación cambia la máscara de interrupciones de CPU ni la habilitación NMI.

El compilador emite RTI para un manejador de interrupciones propio, pero este debe preservar los registros A/X/Y y temporales compartidos que modifique. El ejemplo guarda A/X/Y y solo usa un incremento de byte y un reconocimiento sin argumentos. Si añades llamadas C, revisa también el almacenamiento compartido que utilizan.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo MMC3 muestra marcadores CHR 19/20, 21/22 y 23/24. Las lecturas CIRAM dan A B A B con mirroring vertical y A A B B con horizontal. Los recuentos IRQ son 01→02→03→03: cada una de las tres primeras habilitaciones explícitas permite una interrupción; tras deshabilitar, el recuento no cambia. Cada etapa espera seis fotogramas.

__mapper_irq_ack: MMC3-DEFAULT
MMC3-DEFAULT

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:332

Notas de la declaración (original)
Use the same E000 write as disable; explicitly re-enable when another interrupt is needed.
__mapper_irq_disable

Escribe en $E000 para borrar la petición IRQ MMC3 y deshabilitar las posteriores.

Sintaxis

c
void __mapper_irq_disable(void);

Valor devuelto

No devuelve ningún valor.

Condiciones y límites

Las declaraciones de las funciones intrínsecas están en intrinsics.h. Incluye nes_game.h para las macros nes_mapper_irq_*. Selecciona la placa al compilar con --mapper.

Solo MMC3. Se necesitan un manejador __nes_irq, transiciones válidas de PPU A12, la habilitación IRQ del mapper y __irq_enable() de CPU. El ejemplo activa el renderizado con patrones de fondo en $0000 y de sprites en $1000. Esta lección no verifica la sincronización con una línea exacta en hardware físico.

Tanto el reconocimiento como la deshabilitación MMC3 escriben en $E000. Esto borra la petición actual y deshabilita las siguientes. Vuelve a llamar a __mapper_irq_enable() cuando corresponda para preparar otra interrupción. Ninguna operación cambia la máscara de interrupciones de CPU ni la habilitación NMI.

El compilador emite RTI para un manejador de interrupciones propio, pero este debe preservar los registros A/X/Y y temporales compartidos que modifique. El ejemplo guarda A/X/Y y solo usa un incremento de byte y un reconocimiento sin argumentos. Si añades llamadas C, revisa también el almacenamiento compartido que utilizan.

Ejemplo de uso

c
__irq_disable(); __mapper_irq_disable();

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo MMC3 muestra marcadores CHR 19/20, 21/22 y 23/24. Las lecturas CIRAM dan A B A B con mirroring vertical y A A B B con horizontal. Los recuentos IRQ son 01→02→03→03: cada una de las tres primeras habilitaciones explícitas permite una interrupción; tras deshabilitar, el recuento no cambia. Cada etapa espera seis fotogramas.

__mapper_irq_disable: MMC3-DEFAULT
MMC3-DEFAULT

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:330

Notas de la declaración (original)
Write MMC3 E000 to disable and acknowledge mapper IRQs.
__mapper_irq_enable

Escribe en $E001 para permitir que MMC3 active IRQ al cumplirse la condición del contador. No fija el valor de carga ni solicita recarga.

Sintaxis

c
void __mapper_irq_enable(void);

Valor devuelto

No devuelve ningún valor.

Condiciones y límites

Las declaraciones de las funciones intrínsecas están en intrinsics.h. Incluye nes_game.h para las macros nes_mapper_irq_*. Selecciona la placa al compilar con --mapper.

Solo MMC3. Se necesitan un manejador __nes_irq, transiciones válidas de PPU A12, la habilitación IRQ del mapper y __irq_enable() de CPU. El ejemplo activa el renderizado con patrones de fondo en $0000 y de sprites en $1000. Esta lección no verifica la sincronización con una línea exacta en hardware físico.

Tanto el reconocimiento como la deshabilitación MMC3 escriben en $E000. Esto borra la petición actual y deshabilita las siguientes. Vuelve a llamar a __mapper_irq_enable() cuando corresponda para preparar otra interrupción. Ninguna operación cambia la máscara de interrupciones de CPU ni la habilitación NMI.

El compilador emite RTI para un manejador de interrupciones propio, pero este debe preservar los registros A/X/Y y temporales compartidos que modifique. El ejemplo guarda A/X/Y y solo usa un incremento de byte y un reconocimiento sin argumentos. Si añades llamadas C, revisa también el almacenamiento compartido que utilizan.

Ejemplo de uso

c
__mapper_irq_set(37); __mapper_irq_enable(); __irq_enable();
    for (i=0;i<6;i++) { m_wait(); }
    mapper_result[15]=irq_count;

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo MMC3 muestra marcadores CHR 19/20, 21/22 y 23/24. Las lecturas CIRAM dan A B A B con mirroring vertical y A A B B con horizontal. Los recuentos IRQ son 01→02→03→03: cada una de las tres primeras habilitaciones explícitas permite una interrupción; tras deshabilitar, el recuento no cambia. Cada etapa espera seis fotogramas.

__mapper_irq_enable: MMC3-DEFAULT
MMC3-DEFAULT

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:328

Notas de la declaración (original)
Write MMC3 E001. The CPU IRQ mask and a valid handler are separate caller responsibilities.
__mapper_irq_set

Escribe el valor de carga IRQ MMC3 en $C000 y solicita la siguiente recarga en $C001. Habilitar interrupciones es una operación aparte.

Sintaxis

c
void __mapper_irq_set(u8 scanline);

Parámetros

scanline

Valor de carga del contador de ocho bits, de 0 a 255, no una coordenada Y absoluta. El momento de recarga y las transiciones válidas de PPU A12 determinan cuándo se activa. Cero es válido y no significa «deshabilitar interrupciones».

Valor devuelto

No devuelve ningún valor.

Condiciones y límites

Las declaraciones de las funciones intrínsecas están en intrinsics.h. Incluye nes_game.h para las macros nes_mapper_irq_*. Selecciona la placa al compilar con --mapper.

Solo MMC3. Se necesitan un manejador __nes_irq, transiciones válidas de PPU A12, la habilitación IRQ del mapper y __irq_enable() de CPU. El ejemplo activa el renderizado con patrones de fondo en $0000 y de sprites en $1000. Esta lección no verifica la sincronización con una línea exacta en hardware físico.

Tanto el reconocimiento como la deshabilitación MMC3 escriben en $E000. Esto borra la petición actual y deshabilita las siguientes. Vuelve a llamar a __mapper_irq_enable() cuando corresponda para preparar otra interrupción. Ninguna operación cambia la máscara de interrupciones de CPU ni la habilitación NMI.

El compilador emite RTI para un manejador de interrupciones propio, pero este debe preservar los registros A/X/Y y temporales compartidos que modifique. El ejemplo guarda A/X/Y y solo usa un incremento de byte y un reconocimiento sin argumentos. Si añades llamadas C, revisa también el almacenamiento compartido que utilizan.

Ejemplo de uso

c
__mapper_irq_set(37); __mapper_irq_enable(); __irq_enable();
    for (i=0;i<6;i++) { m_wait(); }
    mapper_result[15]=irq_count;

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo MMC3 muestra marcadores CHR 19/20, 21/22 y 23/24. Las lecturas CIRAM dan A B A B con mirroring vertical y A A B B con horizontal. Los recuentos IRQ son 01→02→03→03: cada una de las tres primeras habilitaciones explícitas permite una interrupción; tras deshabilitar, el recuento no cambia. Cada etapa espera seis fotogramas.

__mapper_irq_set: MMC3-DEFAULT
MMC3-DEFAULT

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:326

Notas de la declaración (original)
Alias the MMC3 IRQ latch/reload operation; other mapper profiles are rejected.
__memcpy

Copia de src a dst el número de bytes indicado. El contador de 16 bits permite transferir más de 255 bytes, por ejemplo una tabla completa en RAM. Lee y escribe en orden creciente de direcciones.

Sintaxis

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

Parámetros

dst

Puntero al primer byte escribible del destino. Reserva espacio contiguo para toda la operación; la función no asigna un búfer.

src

Puntero al primer byte legible del origen. Todo el intervalo debe permanecer accesible y sin cambios durante la copia.

len

Número de bytes como u16 (0–65535), no de elementos. El máximo del tipo no implica que la máquina disponga de un búfer válido de ese tamaño. Todo el intervalo debe caber en el mapa de memoria seleccionado.

Valor devuelto

Ninguno (void).

Condiciones y límites

Un contador a cero no transfiere bytes. Los efectos secundarios de las expresiones de los argumentos sí se ejecutan: el ejemplo pasa dos funciones que devuelven punteros y comprueba que ambas se llaman aunque la longitud sea cero.

Usa regiones de origen y destino que no se solapen. Estas rutinas copian hacia delante y no ofrecen la seguridad de memmove para zonas superpuestas; escribir sobre bytes del origen aún no leídos puede corromper la copia.

La operación termina antes de devolver el control. No cambia bancos de ROM/RAM ni recorta el intervalo al límite del búfer. Mantén estable el mapa de memoria, usa regiones válidas y coordina el acceso con las interrupciones que utilicen los mismos búferes.

En FC, dst es una dirección de CPU, no de PPU. Copiar a RAM de CPU no actualiza CHR, tablas de nombres ni paletas; usa las API de PPU/VRAM para cargarlas. Las rutinas auxiliares generadas comparten argumentos y temporales: no vuelvas a entrar en ellas desde NMI/IRQ mientras se ejecuta otra llamada.

Ejemplo de uso

c
memory_reset();
__memcpy(memory_destination+1,memory_source+1,257);
memory_copy_word=memory_check(257,0); // 257 copied bytes; guards unchanged.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo usa __memcpy para copiar 257 bytes con un patrón conocido desde el desplazamiento 1. memory_check compara los arrays completos de origen y destino; la fila COPY WORD debe mostrar 257. El primer byte queda como protección y también se comprueba que los bytes posteriores al bloque no cambien.

El programa completo inicializa dos búferes de 264 bytes antes de cada operación y comprueba todos los bytes del origen, del destino y de las zonas de protección. Debe mostrar COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 y FAILED 000. WORD/BYTE indican el tamaño del contador, no el de los elementos copiados. Los números proceden de las comprobaciones del ejemplo; estas funciones void no devuelven valores.

Las capturas muestran letras negras sobre blanco en DMG, azules sobre blanco en CGB y azules sobre negro en FC. El ejemplo verifica operaciones de RAM; el texto presenta sus resultados, no una primitiva de dibujo. Otras pruebas en emulador cubren 88 casos por plataforma, con longitudes constantes y calculadas en ejecución de 0, 1, 16, 17, 32, 33, 64, 65 y 255 bytes, más 256 y 257 para los contadores de 16 bits. No se ha probado en hardware real.

__memcpy: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:19

Notas de la declaración (original)
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

Copia un bloque de hasta 255 bytes con un contador de 8 bits. Usa esta variante cuando la longitud quepa en un byte. _small indica el tamaño del contador; no garantiza un tiempo de ejecución ni un tamaño de código concretos.

Sintaxis

c
void __memcpy_small(u8* dst, const u8* src, u8 len);

Parámetros

dst

Puntero al primer byte escribible del destino. Reserva espacio contiguo para toda la operación; la función no asigna un búfer.

src

Puntero al primer byte legible del origen. Todo el intervalo debe permanecer accesible y sin cambios durante la copia.

len

Número de bytes como u8: 0–255. No pases 256 para representar un bloque completo de 256 bytes: esa longitud no cabe en el parámetro. Para 256 bytes o más, usa la función correspondiente sin _small.

Valor devuelto

Ninguno (void).

Condiciones y límites

Un contador a cero no transfiere bytes. Los efectos secundarios de las expresiones de los argumentos sí se ejecutan: el ejemplo pasa dos funciones que devuelven punteros y comprueba que ambas se llaman aunque la longitud sea cero.

Usa regiones de origen y destino que no se solapen. Estas rutinas copian hacia delante y no ofrecen la seguridad de memmove para zonas superpuestas; escribir sobre bytes del origen aún no leídos puede corromper la copia.

La operación termina antes de devolver el control. No cambia bancos de ROM/RAM ni recorta el intervalo al límite del búfer. Mantén estable el mapa de memoria, usa regiones válidas y coordina el acceso con las interrupciones que utilicen los mismos búferes.

En FC, dst es una dirección de CPU, no de PPU. Copiar a RAM de CPU no actualiza CHR, tablas de nombres ni paletas; usa las API de PPU/VRAM para cargarlas. Las rutinas auxiliares generadas comparten argumentos y temporales: no vuelvas a entrar en ellas desde NMI/IRQ mientras se ejecuta otra llamada.

Ejemplo de uso

c
memory_reset();
__memcpy_small(memory_destination+1,memory_source+1,255);
memory_copy_byte=memory_check(255,0); // Maximum representable byte count.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo usa __memcpy_small para copiar 255 bytes con un patrón conocido desde el desplazamiento 1. memory_check compara los arrays completos de origen y destino; la fila COPY BYTE debe mostrar 255. El primer byte queda como protección y también se comprueba que los bytes posteriores al bloque no cambien.

El programa completo inicializa dos búferes de 264 bytes antes de cada operación y comprueba todos los bytes del origen, del destino y de las zonas de protección. Debe mostrar COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 y FAILED 000. WORD/BYTE indican el tamaño del contador, no el de los elementos copiados. Los números proceden de las comprobaciones del ejemplo; estas funciones void no devuelven valores.

Las capturas muestran letras negras sobre blanco en DMG, azules sobre blanco en CGB y azules sobre negro en FC. El ejemplo verifica operaciones de RAM; el texto presenta sus resultados, no una primitiva de dibujo. Otras pruebas en emulador cubren 88 casos por plataforma, con longitudes constantes y calculadas en ejecución de 0, 1, 16, 17, 32, 33, 64, 65 y 255 bytes, más 256 y 257 para los contadores de 16 bits. No se ha probado en hardware real.

__memcpy_small: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:23

Notas de la declaración (original)
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

Rellena el destino con un mismo valor de un byte. El contador de 16 bits resulta útil para vaciar un búfer grande o inicializar una tabla. Por ejemplo, 0x5A escribe 5A 5A 5A…; no repite un entero de varios bytes.

Sintaxis

c
void __memset(u8* dst, u8 value, u16 len);

Parámetros

dst

Puntero al primer byte escribible del destino. Reserva espacio contiguo para toda la operación; la función no asigna un búfer.

value

Byte que se repite en cada dirección del destino: 0–255. Usa 0 para poner el intervalo a cero. Para inicializar un array de u16 con un número distinto de cero, asigna ese valor a cada elemento.

len

Número de bytes como u16 (0–65535), no de elementos. El máximo del tipo no implica que la máquina disponga de un búfer válido de ese tamaño. Todo el intervalo debe caber en el mapa de memoria seleccionado.

Valor devuelto

Ninguno (void).

Condiciones y límites

Un contador a cero no transfiere bytes. Los efectos secundarios de las expresiones de los argumentos sí se ejecutan: el ejemplo pasa dos funciones que devuelven punteros y comprueba que ambas se llaman aunque la longitud sea cero.

La operación termina antes de devolver el control. No cambia bancos de ROM/RAM ni recorta el intervalo al límite del búfer. Mantén estable el mapa de memoria, usa regiones válidas y coordina el acceso con las interrupciones que utilicen los mismos búferes.

En FC, dst es una dirección de CPU, no de PPU. Copiar a RAM de CPU no actualiza CHR, tablas de nombres ni paletas; usa las API de PPU/VRAM para cargarlas. Las rutinas auxiliares generadas comparten argumentos y temporales: no vuelvas a entrar en ellas desde NMI/IRQ mientras se ejecuta otra llamada.

Ejemplo de uso

c
memory_reset();
__memset(memory_destination+1,0x5A,257);
memory_fill_word=memory_check(257,1); // Repeat one byte, not a 16-bit word.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo usa __memset para rellenar 257 bytes desde el desplazamiento 1 con 0x5A. memory_check exige que todos esos bytes valgan 0x5A, que los bytes de protección sigan en 0xCC y que el búfer de origen separado no cambie. La fila FILL WORD debe mostrar 257.

El programa completo inicializa dos búferes de 264 bytes antes de cada operación y comprueba todos los bytes del origen, del destino y de las zonas de protección. Debe mostrar COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 y FAILED 000. WORD/BYTE indican el tamaño del contador, no el de los elementos copiados. Los números proceden de las comprobaciones del ejemplo; estas funciones void no devuelven valores.

Las capturas muestran letras negras sobre blanco en DMG, azules sobre blanco en CGB y azules sobre negro en FC. El ejemplo verifica operaciones de RAM; el texto presenta sus resultados, no una primitiva de dibujo. Otras pruebas en emulador cubren 88 casos por plataforma, con longitudes constantes y calculadas en ejecución de 0, 1, 16, 17, 32, 33, 64, 65 y 255 bytes, más 256 y 257 para los contadores de 16 bits. No se ha probado en hardware real.

__memset: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:21

Notas de la declaración (original)
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

Rellena hasta 255 bytes consecutivos con un mismo valor, usando un contador de 8 bits. Sirve para inicializar un búfer de trabajo pequeño. Si necesitas más de 255 bytes, utiliza __memset.

Sintaxis

c
void __memset_small(u8* dst, u8 value, u8 len);

Parámetros

dst

Puntero al primer byte escribible del destino. Reserva espacio contiguo para toda la operación; la función no asigna un búfer.

value

Byte que se repite en cada dirección del destino: 0–255. Usa 0 para poner el intervalo a cero. Para inicializar un array de u16 con un número distinto de cero, asigna ese valor a cada elemento.

len

Número de bytes como u8: 0–255. No pases 256 para representar un bloque completo de 256 bytes: esa longitud no cabe en el parámetro. Para 256 bytes o más, usa la función correspondiente sin _small.

Valor devuelto

Ninguno (void).

Condiciones y límites

Un contador a cero no transfiere bytes. Los efectos secundarios de las expresiones de los argumentos sí se ejecutan: el ejemplo pasa dos funciones que devuelven punteros y comprueba que ambas se llaman aunque la longitud sea cero.

La operación termina antes de devolver el control. No cambia bancos de ROM/RAM ni recorta el intervalo al límite del búfer. Mantén estable el mapa de memoria, usa regiones válidas y coordina el acceso con las interrupciones que utilicen los mismos búferes.

En FC, dst es una dirección de CPU, no de PPU. Copiar a RAM de CPU no actualiza CHR, tablas de nombres ni paletas; usa las API de PPU/VRAM para cargarlas. Las rutinas auxiliares generadas comparten argumentos y temporales: no vuelvas a entrar en ellas desde NMI/IRQ mientras se ejecuta otra llamada.

Ejemplo de uso

c
memory_reset();
__memset_small(memory_destination+1,0x5A,255);
memory_fill_byte=memory_check(255,1);

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo usa __memset_small para rellenar 255 bytes desde el desplazamiento 1 con 0x5A. memory_check exige que todos esos bytes valgan 0x5A, que los bytes de protección sigan en 0xCC y que el búfer de origen separado no cambie. La fila FILL BYTE debe mostrar 255.

El programa completo inicializa dos búferes de 264 bytes antes de cada operación y comprueba todos los bytes del origen, del destino y de las zonas de protección. Debe mostrar COPY WORD 257, FILL WORD 257, COPY BYTE 255, FILL BYTE 255, COPY16 016, COPY32 032, ZERO CALLS 002 y FAILED 000. WORD/BYTE indican el tamaño del contador, no el de los elementos copiados. Los números proceden de las comprobaciones del ejemplo; estas funciones void no devuelven valores.

Las capturas muestran letras negras sobre blanco en DMG, azules sobre blanco en CGB y azules sobre negro en FC. El ejemplo verifica operaciones de RAM; el texto presenta sus resultados, no una primitiva de dibujo. Otras pruebas en emulador cubren 88 casos por plataforma, con longitudes constantes y calculadas en ejecución de 0, 1, 16, 17, 32, 33, 64, 65 y 255 bytes, más 256 y 257 para los contadores de 16 bits. No se ha probado en hardware real.

__memset_small: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:25

Notas de la declaración (original)
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

Expande una secuencia terminada de piezas de sprite en entradas consecutivas del búfer auxiliar de la página 02. Cada pieza suma sus desplazamientos X/Y de un byte a la posición base y aporta su mosaico y atributos. Devuelve el siguiente índice OAM.

Sintaxis

c
u8 __metasprite_draw(u8 oam_index, u8 base_x, u8 base_y, const u8* metasprite);

Parámetros

oam_index

Índice de sprite entre 0 y 63, no desplazamiento en bytes. La implementación lo multiplica por cuatro con un cursor de 8 bits; los valores fuera de rango se reducen módulo 64 y pueden sobrescribir otra entrada. No se comprueba si está reservada.

base_x

Posición horizontal en píxeles, expresada como byte sin signo; no es una coordenada de patrón.

base_y

Byte Y de OAM. Para situar el borde superior por debajo de la línea cero, pasa su posición menos uno; 239..255 quedan fuera de las 240 líneas visibles.

metasprite

Puntero a registros legibles de cuatro bytes: dx, dy, tile, attr. Un 0xFF en la siguiente posición dx termina la secuencia; no necesita bytes posteriores. Los desplazamientos se suman módulo 256: 0xFE representa −2 y dy=0xFF puede representar −1, pero dx=0xFF está reservado y no puede representar −1. El puntero debe ser válido en la memoria mapeada.

Valor devuelto

Siguiente índice OAM: (oam_index + piezas_escritas) modulo 64. Es un cursor, no la cantidad de piezas dibujadas. Una secuencia vacía devuelve oam_index modulo 64.

Condiciones y límites

Estas operaciones modifican la RAM de CPU 0x0200–0x02FF; no actualizan inmediatamente la OAM del hardware. Transfiera la página completa con __oam_dma. No mantienen las marcas de reserva, las matrices de coordenadas ni el contador de la biblioteca de sprites. Evite que una interrupción modifique o transfiera una página a medio preparar.

No comprueba el número de piezas, los límites de pantalla ni la capacidad. Incluya un terminador y como máximo 64−oam_index piezas. Las coordenadas se reducen módulo 256; tras la entrada 63 se escribe en la 0. Una secuencia vacía no cambia entradas. Este formato difiere del array MetaSpritePart con recuento explícito que usa metasprite_draw en la biblioteca de sprites.

Ejemplo de uso

c
fc_oam_next=__metasprite_draw(5,136,95,fc_oam_pair); // Returns 7 after two entries.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo dedica una fila rotulada a cada operación. SET y MOVE muestran flechas de 8×8 hacia la derecha en (144,16) y (144,32); TILE, un cuadrado verde en (144,48); ATTR, la flecha invertida en ambos ejes en (144,64). HIDE en (144,80) y CLEAR en (144,112) deben quedar vacíos. META muestra dos flechas en (136,96) y (144,96), la segunda reflejada horizontalmente. NEXT 07 indica el siguiente índice tras escribir las entradas 5 y 6. Los bordes son rojos, el cuerpo verde y los detalles de la punta azules. Se comparan 8.704 píxeles por imagen y los bytes OAM transferidos; no se ha probado en hardware físico.

TRANSFER muestra una flecha en (144,128). Este programa envía la página 02 directamente con __oam_dma, incluidos todos los campos preparados por las operaciones individuales de sprites.

__metasprite_draw: PAGE02
PAGE02

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:203

Notas de la declaración (original)
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

Lee la señal digital del micrófono del segundo mando de Famicom desde el bit 2 de $4016. La rutina lee el registro una vez, aplica la máscara 0x04 y normaliza a 0 o 1 sin invertir. No fija los estados de los mandos, graba audio, mide volumen ni reconoce voz.

Sintaxis

c
u8 __mic_read2p(void);

Valor devuelto

Exactamente 1 si $4016 & 0x04 no es cero; en caso contrario, 0. Es el nivel digital muestreado, no una máscara de botones, amplitud, frecuencia ni un recuento de palabras.

Condiciones y límites

Aunque el micrófono pertenece al mando 2, esta rutina accede a $4016, no a $4017. Una lectura del registro también puede avanzar la secuencia serie del mando cuando el estrobo está bajo; no intercales lecturas del micrófono en otro bucle de lectura de ocho bits. No se actualiza ninguna caché de historial de entrada.

El modelo de mandos de KUROSAKI publicado solo devuelve D0 y el bit 6 fijo. Por ello, D1 y el bit del micrófono siempre son 0 en este emulador, incluso con botones normales pulsados. La ejecución incluida verifica la compilación, la entrada normal y esta observación de señal cero. No verifica señales periféricas distintas de cero, tiempos eléctricos ni compatibilidad real; un cero aquí no demuestra que el dispositivo funcione ni que estas funciones sean incapaces de leerlo en hardware.

Ejemplo de uso

c
mic_direct=__mic_read2p(); // A separate sample of the same digital signal.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Este programa compara la entrada normal D0 con D1 y el nivel digital del micrófono. Mantén A en el mando 1 y B en el mando 2 hasta capturar la lectura. NORMAL P1 y NORMAL P2 muestran 001 y 002. En la ejecución KUROSAKI incluida, D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL y MIC DIRECT muestran 000 porque esas líneas no se emulan. Después, los valores quedan fijos. Con periféricos adecuados y sus líneas implementadas, los valores dependen de la entrada real; interprétalos según los bits descritos arriba en lugar de esperar estos ceros.

El modelo de mandos de KUROSAKI publicado solo devuelve D0 y el bit 6 fijo. Por ello, D1 y el bit del micrófono siempre son 0 en este emulador, incluso con botones normales pulsados. La ejecución incluida verifica la compilación, la entrada normal y esta observación de señal cero. No verifica señales periféricas distintas de cero, tiempos eléctricos ni compatibilidad real; un cero aquí no demuestra que el dispositivo funcione ni que estas funciones sean incapaces de leerlo en hardware.

__mic_read2p: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:233

Notas de la declaración (original)
Alias the same normalized microphone input bit as __joypad2p_voice.
__midi_clock

Envía un byte de reloj F8. Quien llama programa los pulsos repetidos.

Sintaxis

c
void __midi_clock(void);

Valor devuelto

Sin valor de retorno. Modifica registros de sonido o el estado del controlador.

Condiciones y límites

Requiere un circuito externo que conecte $4016 D0 a TX y $4017 D4 a RX. Un mando normal no ofrece MIDI y estas llamadas no tocan la APU interna. Detén reproducción/DMA DMC y bloquea NMI e IRQ durante las transferencias; evita accesos simultáneos a puertos y memoria temporal compartida. Los 57 ciclos por bit NTSC dan unos 31.400 bps, aproximadamente +0,5 % frente a 31.250. No se han verificado tiempos PAL ni comunicación de ida y vuelta con un adaptador físico.

Ejemplo de uso

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

Resultado esperado

La pantalla muestra EXTERNAL PORT DATA y no suena ninguna voz interna. Los bytes transmitidos en hexadecimal son 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. La comprobación exige celdas de transmisión NTSC de 57 ciclos y lecturas separadas por 57 ciclos. El 00 recibido procede de la entrada no conectada del emulador y no demuestra comunicación externa.

__midi_clock: NTSC
NTSC

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:304

Notas de la declaración (original)
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

Envía un byte de tiempo real Continue FB sin operar el controlador musical interno.

Sintaxis

c
void __midi_continue(void);

Valor devuelto

Sin valor de retorno. Modifica registros de sonido o el estado del controlador.

Condiciones y límites

Requiere un circuito externo que conecte $4016 D0 a TX y $4017 D4 a RX. Un mando normal no ofrece MIDI y estas llamadas no tocan la APU interna. Detén reproducción/DMA DMC y bloquea NMI e IRQ durante las transferencias; evita accesos simultáneos a puertos y memoria temporal compartida. Los 57 ciclos por bit NTSC dan unos 31.400 bps, aproximadamente +0,5 % frente a 31.250. No se han verificado tiempos PAL ni comunicación de ida y vuelta con un adaptador físico.

Ejemplo de uso

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

Resultado esperado

La pantalla muestra EXTERNAL PORT DATA y no suena ninguna voz interna. Los bytes transmitidos en hexadecimal son 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. La comprobación exige celdas de transmisión NTSC de 57 ciclos y lecturas separadas por 57 ciclos. El 00 recibido procede de la entrada no conectada del emulador y no demuestra comunicación externa.

__midi_continue: NTSC
NTSC

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:308

Notas de la declaración (original)
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

Envía cambio de control como 0xB0 | (ch & 0x0F), número de controlador y valor.

Sintaxis

c
void __midi_control_change(u8 ch, u8 cc, u8 value);

Parámetros

ch

Canal MIDI de 0 a 15; el nibble bajo entra en el byte de estado. Cero corresponde al canal MIDI mostrado como 1.

cc

Número de controlador de 0 a 127; 7 es volumen de canal.

value

Valor de controlador de 0 a 127; los bits superiores no se borran automáticamente.

Valor devuelto

Sin valor de retorno. Modifica registros de sonido o el estado del controlador.

Condiciones y límites

Requiere un circuito externo que conecte $4016 D0 a TX y $4017 D4 a RX. Un mando normal no ofrece MIDI y estas llamadas no tocan la APU interna. Detén reproducción/DMA DMC y bloquea NMI e IRQ durante las transferencias; evita accesos simultáneos a puertos y memoria temporal compartida. Los 57 ciclos por bit NTSC dan unos 31.400 bps, aproximadamente +0,5 % frente a 31.250. No se han verificado tiempos PAL ni comunicación de ida y vuelta con un adaptador físico.

Ejemplo de uso

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

Resultado esperado

La pantalla muestra EXTERNAL PORT DATA y no suena ninguna voz interna. Los bytes transmitidos en hexadecimal son 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. La comprobación exige celdas de transmisión NTSC de 57 ciclos y lecturas separadas por 57 ciclos. El 00 recibido procede de la entrada no conectada del emulador y no demuestra comunicación externa.

__midi_control_change: NTSC
NTSC

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:300

Notas de la declaración (original)
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

Espera una entrada de recepción baja y muestrea ocho bits a intervalos temporizados.

Sintaxis

c
u8 __midi_in_byte(void);

Valor devuelto

Devuelve el u8 recibido. No hay tiempo máximo, validación de trama ni búfer de recepción. Una entrada alta bloquea indefinidamente; una mantenida baja se lee como cero.

Condiciones y límites

Requiere un circuito externo que conecte $4016 D0 a TX y $4017 D4 a RX. Un mando normal no ofrece MIDI y estas llamadas no tocan la APU interna. Detén reproducción/DMA DMC y bloquea NMI e IRQ durante las transferencias; evita accesos simultáneos a puertos y memoria temporal compartida. Los 57 ciclos por bit NTSC dan unos 31.400 bps, aproximadamente +0,5 % frente a 31.250. No se han verificado tiempos PAL ni comunicación de ida y vuelta con un adaptador físico.

Ejemplo de uso

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

Resultado esperado

La pantalla muestra EXTERNAL PORT DATA y no suena ninguna voz interna. Los bytes transmitidos en hexadecimal son 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. La comprobación exige celdas de transmisión NTSC de 57 ciclos y lecturas separadas por 57 ciclos. El 00 recibido procede de la entrada no conectada del emulador y no demuestra comunicación externa.

__midi_in_byte: NTSC
NTSC

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:294

Notas de la declaración (original)
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

Envía note-off en tres bytes: 0x80 | (ch & 0x0F), note, velocity.

Sintaxis

c
void __midi_note_off(u8 ch, u8 note, u8 velocity);

Parámetros

ch

Canal MIDI de 0 a 15; el nibble bajo entra en el byte de estado. Cero corresponde al canal MIDI mostrado como 1.

note

Nota MIDI de 0 a 127; 69 es A4. No se enmascara a siete bits, así que quien llama debe respetar el rango.

velocity

Velocidad de pulsación de 0 a 127; no se enmascara automáticamente a siete bits.

Valor devuelto

Sin valor de retorno. Modifica registros de sonido o el estado del controlador.

Condiciones y límites

Requiere un circuito externo que conecte $4016 D0 a TX y $4017 D4 a RX. Un mando normal no ofrece MIDI y estas llamadas no tocan la APU interna. Detén reproducción/DMA DMC y bloquea NMI e IRQ durante las transferencias; evita accesos simultáneos a puertos y memoria temporal compartida. Los 57 ciclos por bit NTSC dan unos 31.400 bps, aproximadamente +0,5 % frente a 31.250. No se han verificado tiempos PAL ni comunicación de ida y vuelta con un adaptador físico.

Ejemplo de uso

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

Resultado esperado

La pantalla muestra EXTERNAL PORT DATA y no suena ninguna voz interna. Los bytes transmitidos en hexadecimal son 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. La comprobación exige celdas de transmisión NTSC de 57 ciclos y lecturas separadas por 57 ciclos. El 00 recibido procede de la entrada no conectada del emulador y no demuestra comunicación externa.

__midi_note_off: NTSC
NTSC

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:298

Notas de la declaración (original)
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

Envía note-on en tres bytes: 0x90 | (ch & 0x0F), note, velocity.

Sintaxis

c
void __midi_note_on(u8 ch, u8 note, u8 velocity);

Parámetros

ch

Canal MIDI de 0 a 15; el nibble bajo entra en el byte de estado. Cero corresponde al canal MIDI mostrado como 1.

note

Nota MIDI de 0 a 127; 69 es A4. No se enmascara a siete bits, así que quien llama debe respetar el rango.

velocity

Velocidad de pulsación de 0 a 127; no se enmascara automáticamente a siete bits.

Valor devuelto

Sin valor de retorno. Modifica registros de sonido o el estado del controlador.

Condiciones y límites

Requiere un circuito externo que conecte $4016 D0 a TX y $4017 D4 a RX. Un mando normal no ofrece MIDI y estas llamadas no tocan la APU interna. Detén reproducción/DMA DMC y bloquea NMI e IRQ durante las transferencias; evita accesos simultáneos a puertos y memoria temporal compartida. Los 57 ciclos por bit NTSC dan unos 31.400 bps, aproximadamente +0,5 % frente a 31.250. No se han verificado tiempos PAL ni comunicación de ida y vuelta con un adaptador físico.

Ejemplo de uso

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

Resultado esperado

La pantalla muestra EXTERNAL PORT DATA y no suena ninguna voz interna. Los bytes transmitidos en hexadecimal son 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. La comprobación exige celdas de transmisión NTSC de 57 ciclos y lecturas separadas por 57 ciclos. El 00 recibido procede de la entrada no conectada del emulador y no demuestra comunicación externa.

__midi_note_on: NTSC
NTSC

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:296

Notas de la declaración (original)
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

Transmite síncronamente un byte 8N1: inicio bajo, ocho bits de datos desde el menos significativo y parada alta.

Sintaxis

c
void __midi_out_byte(u8 byte);

Parámetros

byte

Byte bruto de 0 a 255, incluidos bytes de estado o datos.

Valor devuelto

Sin valor de retorno. Modifica registros de sonido o el estado del controlador.

Condiciones y límites

Requiere un circuito externo que conecte $4016 D0 a TX y $4017 D4 a RX. Un mando normal no ofrece MIDI y estas llamadas no tocan la APU interna. Detén reproducción/DMA DMC y bloquea NMI e IRQ durante las transferencias; evita accesos simultáneos a puertos y memoria temporal compartida. Los 57 ciclos por bit NTSC dan unos 31.400 bps, aproximadamente +0,5 % frente a 31.250. No se han verificado tiempos PAL ni comunicación de ida y vuelta con un adaptador físico.

Ejemplo de uso

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

Resultado esperado

La pantalla muestra EXTERNAL PORT DATA y no suena ninguna voz interna. Los bytes transmitidos en hexadecimal son 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. La comprobación exige celdas de transmisión NTSC de 57 ciclos y lecturas separadas por 57 ciclos. El 00 recibido procede de la entrada no conectada del emulador y no demuestra comunicación externa.

__midi_out_byte: NTSC
NTSC

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:292

Notas de la declaración (original)
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

Envía cambio de programa como 0xC0 | (ch & 0x0F) seguido del número de programa.

Sintaxis

c
void __midi_program_change(u8 ch, u8 program);

Parámetros

ch

Canal MIDI de 0 a 15; el nibble bajo entra en el byte de estado. Cero corresponde al canal MIDI mostrado como 1.

program

Programa de 0 a 127; la asignación de instrumentos depende del receptor.

Valor devuelto

Sin valor de retorno. Modifica registros de sonido o el estado del controlador.

Condiciones y límites

Requiere un circuito externo que conecte $4016 D0 a TX y $4017 D4 a RX. Un mando normal no ofrece MIDI y estas llamadas no tocan la APU interna. Detén reproducción/DMA DMC y bloquea NMI e IRQ durante las transferencias; evita accesos simultáneos a puertos y memoria temporal compartida. Los 57 ciclos por bit NTSC dan unos 31.400 bps, aproximadamente +0,5 % frente a 31.250. No se han verificado tiempos PAL ni comunicación de ida y vuelta con un adaptador físico.

Ejemplo de uso

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

Resultado esperado

La pantalla muestra EXTERNAL PORT DATA y no suena ninguna voz interna. Los bytes transmitidos en hexadecimal son 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. La comprobación exige celdas de transmisión NTSC de 57 ciclos y lecturas separadas por 57 ciclos. El 00 recibido procede de la entrada no conectada del emulador y no demuestra comunicación externa.

__midi_program_change: NTSC
NTSC

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:302

Notas de la declaración (original)
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

Envía un byte de tiempo real Start FA sin cambiar los punteros de canción del juego.

Sintaxis

c
void __midi_start(void);

Valor devuelto

Sin valor de retorno. Modifica registros de sonido o el estado del controlador.

Condiciones y límites

Requiere un circuito externo que conecte $4016 D0 a TX y $4017 D4 a RX. Un mando normal no ofrece MIDI y estas llamadas no tocan la APU interna. Detén reproducción/DMA DMC y bloquea NMI e IRQ durante las transferencias; evita accesos simultáneos a puertos y memoria temporal compartida. Los 57 ciclos por bit NTSC dan unos 31.400 bps, aproximadamente +0,5 % frente a 31.250. No se han verificado tiempos PAL ni comunicación de ida y vuelta con un adaptador físico.

Ejemplo de uso

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

Resultado esperado

La pantalla muestra EXTERNAL PORT DATA y no suena ninguna voz interna. Los bytes transmitidos en hexadecimal son 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. La comprobación exige celdas de transmisión NTSC de 57 ciclos y lecturas separadas por 57 ciclos. El 00 recibido procede de la entrada no conectada del emulador y no demuestra comunicación externa.

__midi_start: NTSC
NTSC

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:306

Notas de la declaración (original)
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

Envía un byte de tiempo real Stop FC. No silencia directamente la APU ni las notas enviadas antes.

Sintaxis

c
void __midi_stop(void);

Valor devuelto

Sin valor de retorno. Modifica registros de sonido o el estado del controlador.

Condiciones y límites

Requiere un circuito externo que conecte $4016 D0 a TX y $4017 D4 a RX. Un mando normal no ofrece MIDI y estas llamadas no tocan la APU interna. Detén reproducción/DMA DMC y bloquea NMI e IRQ durante las transferencias; evita accesos simultáneos a puertos y memoria temporal compartida. Los 57 ciclos por bit NTSC dan unos 31.400 bps, aproximadamente +0,5 % frente a 31.250. No se han verificado tiempos PAL ni comunicación de ida y vuelta con un adaptador físico.

Ejemplo de uso

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

Resultado esperado

La pantalla muestra EXTERNAL PORT DATA y no suena ninguna voz interna. Los bytes transmitidos en hexadecimal son 7E; C2 05; B2 07 64; 92 45 60; 82 45 00; F8 FA FB FC. La comprobación exige celdas de transmisión NTSC de 57 ciclos y lecturas separadas por 57 ciclos. El 00 recibido procede de la entrada no conectada del emulador y no demuestra comunicación externa.

__midi_stop: NTSC
NTSC

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:310

Notas de la declaración (original)
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

Asigna las cuatro regiones lógicas de tablas de nombres a las dos páginas de RAM de pantalla de la consola, para organizar el desplazamiento o seleccionar una página de pantalla única.

Sintaxis

c
void __mirroring_set(u8 mode);

Parámetros

mode

Los números de modo varían según la placa. MMC1/SUROM: 0=pantalla única inferior, 1=pantalla única superior, 2=vertical, 3=horizontal. MMC3: 0=vertical, 1=horizontal. MMC5: 0=horizontal, 1=vertical, 2=pantalla única inferior, 3=pantalla única superior. AxROM: 0=pantalla única inferior, 1=pantalla única superior. FME7: 0=vertical, 1=horizontal, 2=pantalla única inferior, 3=pantalla única superior. MMC3/AxROM usan el bit bajo; los demás, los dos bits bajos.

Valor devuelto

No devuelve ningún valor.

Condiciones y límites

Las declaraciones de las funciones intrínsecas están en intrinsics.h. Incluye nes_game.h para las macros nes_mapper_irq_*. Selecciona la placa al compilar con --mapper.

El mirroring vertical asigna las cuatro regiones como A B A B y el horizontal como A A B B. Pantalla única inferior da A A A A; superior, B B B B. Esto cambia el mapeo de memoria; no voltea ni copia gráficos del fondo. Las placas no admitidas, incluido NROM con mirroring fijo, se rechazan al compilar.

MMC1 escribe el control $0C|mode, seleccionando una ventana PRG superior fija de 16 KiB y modo CHR de 8 KiB. SUROM también actualiza su copia del control. AxROM cambia el bit de pantalla conservando el banco PRG registrado en ejecución. Si también escribes directamente en registros del mapper, mantén la coherencia con el estado de banco del programa.

Para MMC5, la API escribe $50/$44/$00/$55 en $5105. Estas distribuciones solo usan las dos páginas CIRAM de la consola. ExRAM y el modo de relleno quedan fuera de esta API. KUROSAKI admite las dieciséis distribuciones solo CIRAM; el renderizado MMC5 de ExRAM y relleno sigue sin estar admitido.

Cambia --mapper=mmc3 del ejemplo por mmc1, mmc5, axrom o fme7 para comprobar cada placa. El ejemplo inicializa los bancos CHR de fuente MMC5/FME7 mediante sus registros de placa.

Ejemplo de uso

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++; }
    }

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

La lección escribe 17 en la página RAM física inferior y 34 en la superior, y lee las cuatro regiones lógicas. A representa la página inferior y B la superior. Las regiones con la misma letra comparten RAM: escribir a través de una se refleja en las otras. Las columnas son $2000, $2400, $2800 y $2C00; las filas, los modos 0..3. El texto se muestra después de la lectura.

__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

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:322

Notas de la declaración (original)
Emit the selected mapper mirroring operation; available modes and writable control depend on the board.
__mul16x8

Calcula una multiplicación sin signo.

Sintaxis

c
u16 __mul16x8(u16 a, u8 b);

Parámetros

a

Palabra sin signo de 0 a 65535.

b

Coeficiente sin signo de 0 a 255.

Valor devuelto

Devuelve los 16 bits bajos de a*b.

Condiciones y límites

En FC, los nombres q8_8/q1_7 usan productos enteros sin el desplazamiento de escala de punto fijo. Las funciones Q1.7 son alias de sus operaciones con signo correspondientes. El código que llama gestiona las unidades; los resultados con signo se devuelven como patrones de bits u16.

Ejemplo de uso

c
// Compare literal coefficients with values read from RAM.
result[2]=__mul16x8(513,127);
v0=513;v1=127;
result[3]=__mul16x8(v0,v1);

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Compara entradas literales con valores guardados en RAM. Los resultados representativos se muestran como valores de 16 bits sin signo; los negativos usan patrones en complemento a dos. GB ajusta la escala de los productos de punto fijo, mientras FC devuelve productos enteros, por lo que entradas idénticas pueden dar resultados distintos.

El fragmento guarda estos valores de 16 bits: result[2]=65151, result[3]=65151. Cada cálculo con entrada literal coincide con su equivalente con entrada en RAM. La pantalla muestra valores representativos; el array result guarda todos los resultados.

__mul16x8: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:51

Notas de la declaración (original)
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

Multiplica dos bytes sin signo y devuelve el byte alto.

Sintaxis

c
u8 __mul8x8_hi(u8 a, u8 b);

Parámetros

a

Byte sin signo de 0 a 255.

b

Byte sin signo de 0 a 255.

Valor devuelto

floor(a*b/256), en el rango 0..254.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Compara entradas literales con valores guardados en RAM. Los resultados representativos se muestran como valores de 16 bits sin signo; los negativos usan patrones en complemento a dos. GB ajusta la escala de los productos de punto fijo, mientras FC devuelve productos enteros, por lo que entradas idénticas pueden dar resultados distintos.

El fragmento guarda estos valores de 16 bits: result[0]=254, result[1]=254. Cada cálculo con entrada literal coincide con su equivalente con entrada en RAM. La pantalla muestra valores representativos; el array result guarda todos los resultados.

__mul8x8_hi: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:49

Notas de la declaración (original)
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

Escribe directamente un byte de tile en la tabla de nombres 0, en 0x2000 + y*32 + x. Reinicia el latch antes de fijar la dirección. No escribe gráficos ni atributos de paleta y no recorta las coordenadas.

Sintaxis

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

Parámetros

x / y

Coordenadas de tiles, no píxeles. Use x=0..31 e y=0..29 para el mapa visible de 32×30 tiles; cada tile ocupa 8×8 píxeles. Los argumentos de un byte no se limitan automáticamente y pueden escribir fuera del área prevista.

tile

Número de tile 0..255 en la tabla de patrones de fondo seleccionada por PPUCTRL. Selecciona gráficos CHR existentes, no un color de píxel ni un puntero de origen.

Valor devuelto

Ninguno (void).

Condiciones y límites

El acceso directo a PPU requiere desactivar el renderizado o completar toda la operación dentro de un VBlank seguro. Estas funciones no esperan, encolan, bloquean NMI ni restauran el desplazamiento. Evite que una interrupción cambie el latch o la dirección compartidos durante la llamada. El bit 2 de PPUCTRL selecciona un incremento de destino de 1 o 32 por byte. La PPU gestiona los espejos y el retorno de direcciones; no se puede escribir en CHR-ROM. Restablezca el desplazamiento deseado antes de renderizar.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

c
__nametable_put(2,12,1); // Blue solid marker at (16,96).

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__nametable_put: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:124

Notas de la declaración (original)
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

Selecciona la tabla lógica nt & 3 y escribe un tile en 0x2000 + (nt & 3)*0x400 + y*32 + x. Reinicia el latch antes de escribir la dirección. El mirroring del cartucho puede hacer que distintos números lógicos compartan memoria física.

Sintaxis

c
void __nametable_put_nt(u8 nt, u8 x, u8 y, u8 tile);

Parámetros

nt

Selector de tabla lógica. Solo usa los dos bits inferiores (nt & 3): 4 selecciona la tabla 0 y 7 la tabla 3. No asigna almacenamiento independiente ni cambia el mirroring del mapper.

x / y

Coordenadas de tiles, no píxeles. Use x=0..31 e y=0..29 para el mapa visible de 32×30 tiles; cada tile ocupa 8×8 píxeles. Los argumentos de un byte no se limitan automáticamente y pueden escribir fuera del área prevista.

tile

Número de tile 0..255 en la tabla de patrones de fondo seleccionada por PPUCTRL. Selecciona gráficos CHR existentes, no un color de píxel ni un puntero de origen.

Valor devuelto

Ninguno (void).

Condiciones y límites

El acceso directo a PPU requiere desactivar el renderizado o completar toda la operación dentro de un VBlank seguro. Estas funciones no esperan, encolan, bloquean NMI ni restauran el desplazamiento. Evite que una interrupción cambie el latch o la dirección compartidos durante la llamada. El bit 2 de PPUCTRL selecciona un incremento de destino de 1 o 32 por byte. La PPU gestiona los espejos y el retorno de direcciones; no se puede escribir en CHR-ROM. Restablezca el desplazamiento deseado antes de renderizar.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

c
__nametable_put_nt(4,10,12,2); // Red left-half marker at (80,96), table 0.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__nametable_put_nt: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:126

Notas de la declaración (original)
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

Rellena un rectángulo de w por h tiles en la tabla de nombres 0 desde (x,y). Reinicia el latch y la dirección en cada fila, escribe w copias de tile y avanza 32 bytes hasta el inicio de la fila siguiente. No cambia los atributos de paleta.

Sintaxis

c
void __nametable_rect(u8 x, u8 y, u8 w, u8 h, u8 tile);

Parámetros

x / y

Coordenadas de tiles, no píxeles. Use x=0..31 e y=0..29 para el mapa visible de 32×30 tiles; cada tile ocupa 8×8 píxeles. Los argumentos de un byte no se limitan automáticamente y pueden escribir fuera del área prevista.

w / h

Anchura y altura en tiles, cada una un byte sin signo. Mantenga x+w<=32 e y+h<=30. Altura cero no accede a la PPU. Anchura cero no escribe tiles, pero reinicia el latch y fija las h direcciones de fila. Use incremento PPU 1 para filas horizontales.

tile

Número de tile 0..255 en la tabla de patrones de fondo seleccionada por PPUCTRL. Selecciona gráficos CHR existentes, no un color de píxel ni un puntero de origen.

Valor devuelto

Ninguno (void).

Condiciones y límites

El acceso directo a PPU requiere desactivar el renderizado o completar toda la operación dentro de un VBlank seguro. Estas funciones no esperan, encolan, bloquean NMI ni restauran el desplazamiento. Evite que una interrupción cambie el latch o la dirección compartidos durante la llamada. El bit 2 de PPUCTRL selecciona un incremento de destino de 1 o 32 por byte. La PPU gestiona los espejos y el retorno de direcciones; no se puede escribir en CHR-ROM. Restablezca el desplazamiento deseado antes de renderizar.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

c
__nametable_rect(2,6,8,4,1); // Solid green rectangle at (16,48), 64x32 pixels.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__nametable_rect: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:129

Notas de la declaración (original)
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

Rellena w tiles por fila durante h filas en la tabla lógica nt & 3. La dirección inicial es 0x2000 + (nt & 3)*0x400 + y*32 + x; cada fila comienza 32 bytes después. Solo escribe tiles, sin actualizar atributos, recortar ni configurar el mirroring.

Sintaxis

c
void __nametable_rect_nt(u8 nt, u8 x, u8 y, u8 w, u8 h, u8 tile);

Parámetros

nt

Selector de tabla lógica. Solo usa los dos bits inferiores (nt & 3): 4 selecciona la tabla 0 y 7 la tabla 3. No asigna almacenamiento independiente ni cambia el mirroring del mapper.

x / y

Coordenadas de tiles, no píxeles. Use x=0..31 e y=0..29 para el mapa visible de 32×30 tiles; cada tile ocupa 8×8 píxeles. Los argumentos de un byte no se limitan automáticamente y pueden escribir fuera del área prevista.

w / h

Anchura y altura en tiles, cada una un byte sin signo. Mantenga x+w<=32 e y+h<=30. Altura cero no accede a la PPU. Anchura cero no escribe tiles, pero reinicia el latch y fija las h direcciones de fila. Use incremento PPU 1 para filas horizontales.

tile

Número de tile 0..255 en la tabla de patrones de fondo seleccionada por PPUCTRL. Selecciona gráficos CHR existentes, no un color de píxel ni un puntero de origen.

Valor devuelto

Ninguno (void).

Condiciones y límites

El acceso directo a PPU requiere desactivar el renderizado o completar toda la operación dentro de un VBlank seguro. Estas funciones no esperan, encolan, bloquean NMI ni restauran el desplazamiento. Evite que una interrupción cambie el latch o la dirección compartidos durante la llamada. El bit 2 de PPUCTRL selecciona un incremento de destino de 1 o 32 por byte. La PPU gestiona los espejos y el retorno de direcciones; no se puede escribir en CHR-ROM. Restablezca el desplazamiento deseado antes de renderizar.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

c
__nametable_rect_nt(4,18,6,8,4,3); // nt&3=0; red triangle tiles at (144,48).

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__nametable_rect_nt: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:131

Notas de la declaración (original)
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

Desactiva la generación de NMI en la PPU: borra el bit 7 de la copia de PPUCTRL del compilador y la escribe en $2000. Conserva los demás bits. No cambia la máscara IRQ de la CPU ni desactiva el dibujo.

Sintaxis

c
void __nmi_disable(void);

Valor devuelto

Ninguno (void).

Condiciones y límites

Estas funciones usan la copia de PPUCTRL, sin leer el hardware; las escrituras directas en $2000 no actualizan esa copia. Activar NMI con el indicador VBlank establecido puede provocarla inmediatamente. Desactivar su generación en la PPU no cancela una solicitud ya capturada por la CPU.

NESdev: NMI

NESdev: interrupciones de CPU

Ejemplo de uso

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.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa distingue la máscara IRQ de la CPU del control NMI de la PPU. En azul sobre negro deben aparecer IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 y RESTORED I = 000. Cada número es el bit I extraído, no el byte de estado completo. FAILED CHECKS debe ser 000.

Ejecute al menos 360 fotogramas para completar la demostración de 256 esperas. NMI DELTA 3 WAITS debe ser 003 incluso con IRQ bloqueada; NMI DELTA 256 WAITS debe ser 000 por el retorno cíclico del byte. NMI OFF DELTA también debe ser 000. El programa usa el manejador predeterminado sin runtime.c. Hay 21 comprobaciones de estado adicionales, incluida la restauración de indicadores y esperas observadas durante un tiempo acotado con NMI desactivada o un manejador propio vacío. Se verifica KUROSAKI/NROM, no hardware real.

__nmi_disable: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:174

Notas de la declaración (original)
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

Activa la NMI generada por la PPU: pone a 1 el bit 7 de la copia de PPUCTRL que mantiene el compilador y la escribe en $2000. Conserva los demás bits de esa copia y la máscara IRQ de la CPU. No activa el dibujo del fondo ni de los sprites.

Sintaxis

c
void __nmi_enable(void);

Valor devuelto

Ninguno (void).

Condiciones y límites

Estas funciones usan la copia de PPUCTRL, sin leer el hardware; las escrituras directas en $2000 no actualizan esa copia. Activar NMI con el indicador VBlank establecido puede provocarla inmediatamente. Desactivar su generación en la PPU no cancela una solicitud ya capturada por la CPU.

NESdev: NMI

NESdev: interrupciones de CPU

Ejemplo de uso

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.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa distingue la máscara IRQ de la CPU del control NMI de la PPU. En azul sobre negro deben aparecer IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 y RESTORED I = 000. Cada número es el bit I extraído, no el byte de estado completo. FAILED CHECKS debe ser 000.

Ejecute al menos 360 fotogramas para completar la demostración de 256 esperas. NMI DELTA 3 WAITS debe ser 003 incluso con IRQ bloqueada; NMI DELTA 256 WAITS debe ser 000 por el retorno cíclico del byte. NMI OFF DELTA también debe ser 000. El programa usa el manejador predeterminado sin runtime.c. Hay 21 comprobaciones de estado adicionales, incluida la restauración de indicadores y esperas observadas durante un tiempo acotado con NMI desactivada o un manejador propio vacío. Se verifica KUROSAKI/NROM, no hardware real.

__nmi_enable: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:172

Notas de la declaración (original)
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

Lee el contador NMI actual del compilador sin esperar ni borrarlo. Aunque el nombre diga ready, devuelve un contador de un byte, no un indicador booleano. Compárelo con un valor guardado para detectar nuevas ejecuciones del manejador NMI predeterminado.

Sintaxis

c
u8 __nmi_ready(void);

Valor devuelto

Un byte sin signo de 0 a 255 que el manejador NMI predeterminado incrementa módulo 256. Cero es válido. Tras 256 incrementos, el valor actual coincide de nuevo con el guardado; un sondeo poco frecuente no permite contar intervalos arbitrariamente largos.

Condiciones y límites

Este contador pertenece al manejador NMI predeterminado del compilador. Definir __nes_nmi lo sustituye. runtime.c usa sus propios nes_nmi_counter y nes_wait_nmi. Un manejador propio vacío no avanza el contador del compilador y __nmi_wait no retorna. Active NMI y espere fuera del manejador NMI.

NESdev: NMI

NESdev: interrupciones de CPU

Ejemplo de uso

c
before_count=__nmi_ready(); // A wrapping byte, not a Boolean ready flag.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa distingue la máscara IRQ de la CPU del control NMI de la PPU. En azul sobre negro deben aparecer IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 y RESTORED I = 000. Cada número es el bit I extraído, no el byte de estado completo. FAILED CHECKS debe ser 000.

Ejecute al menos 360 fotogramas para completar la demostración de 256 esperas. NMI DELTA 3 WAITS debe ser 003 incluso con IRQ bloqueada; NMI DELTA 256 WAITS debe ser 000 por el retorno cíclico del byte. NMI OFF DELTA también debe ser 000. El programa usa el manejador predeterminado sin runtime.c. Hay 21 comprobaciones de estado adicionales, incluida la restauración de indicadores y esperas observadas durante un tiempo acotado con NMI desactivada o un manejador propio vacío. Se verifica KUROSAKI/NROM, no hardware real.

__nmi_ready: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:170

Notas de la declaración (original)
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

Lee el contador NMI del compilador al entrar y espera en un bucle hasta que cambie. Con el manejador predeterminado, espera a que termine una ejecución posterior del manejador, incluido el procesamiento de la cola VRAM ya confirmada. No activa NMI, no confirma comandos y no tiene tiempo límite.

Sintaxis

c
void __nmi_wait(void);

Valor devuelto

Ninguno (void).

Condiciones y límites

Este contador pertenece al manejador NMI predeterminado del compilador. Definir __nes_nmi lo sustituye. runtime.c usa sus propios nes_nmi_counter y nes_wait_nmi. Un manejador propio vacío no avanza el contador del compilador y __nmi_wait no retorna. Active NMI y espere fuera del manejador NMI.

NESdev: NMI

NESdev: interrupciones de CPU

Ejemplo de uso

c
__nmi_wait();__nmi_wait();__nmi_wait();
delta=(u8)(__nmi_ready()-before_count); // Three completed default NMIs.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa distingue la máscara IRQ de la CPU del control NMI de la PPU. En azul sobre negro deben aparecer IRQ DISABLED I = 001, IRQ ENABLED I = 000, SAVED SECTION I = 001 y RESTORED I = 000. Cada número es el bit I extraído, no el byte de estado completo. FAILED CHECKS debe ser 000.

Ejecute al menos 360 fotogramas para completar la demostración de 256 esperas. NMI DELTA 3 WAITS debe ser 003 incluso con IRQ bloqueada; NMI DELTA 256 WAITS debe ser 000 por el retorno cíclico del byte. NMI OFF DELTA también debe ser 000. El programa usa el manejador predeterminado sin runtime.c. Hay 21 comprobaciones de estado adicionales, incluida la restauración de indicadores y esperas observadas durante un tiempo acotado con NMI desactivada o un manejador propio vacío. Se verifica KUROSAKI/NROM, no hardware real.

__nmi_wait: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:168

Notas de la declaración (original)
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

Oculta los 64 sprites del búfer auxiliar de la página 02 poniendo cada byte Y a 0xF0. Conserva los números de mosaico, los atributos y las coordenadas X.

Sintaxis

c
void __oam_clear(void);

Valor devuelto

Ninguno (void).

Condiciones y límites

Estas operaciones modifican la RAM de CPU 0x0200–0x02FF; no actualizan inmediatamente la OAM del hardware. Transfiera la página completa con __oam_dma. No mantienen las marcas de reserva, las matrices de coordenadas ni el contador de la biblioteca de sprites. Evite que una interrupción modifique o transfiera una página a medio preparar.

Ejemplo de uso

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.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo dedica una fila rotulada a cada operación. SET y MOVE muestran flechas de 8×8 hacia la derecha en (144,16) y (144,32); TILE, un cuadrado verde en (144,48); ATTR, la flecha invertida en ambos ejes en (144,64). HIDE en (144,80) y CLEAR en (144,112) deben quedar vacíos. META muestra dos flechas en (136,96) y (144,96), la segunda reflejada horizontalmente. NEXT 07 indica el siguiente índice tras escribir las entradas 5 y 6. Los bordes son rojos, el cuerpo verde y los detalles de la punta azules. Se comparan 8.704 píxeles por imagen y los bytes OAM transferidos; no se ha probado en hardware físico.

TRANSFER muestra una flecha en (144,128). Este programa envía la página 02 directamente con __oam_dma, incluidos todos los campos preparados por las operaciones individuales de sprites.

__oam_clear: PAGE02
PAGE02

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:190

Notas de la declaración (original)
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

Transfiere los 256 bytes de la página auxiliar 0x0200–0x02FF a la OAM del hardware, desde la entrada 0. La CPU se detiene durante la transferencia y continúa al finalizar.

Sintaxis

c
void __oam_dma(void);

Valor devuelto

Ninguno (void).

Condiciones y límites

Llame durante VBlank o con el renderizado desactivado; ninguna función DMA espera a VBlank. Ambas ponen OAMADDR a cero antes de transferir. El hardware suele detener la CPU 513 o 514 ciclos; la actividad DMC puede alargarlo. KUROSAKI lo aproxima a 513 ciclos, por lo que verificar la imagen no confirma los tiempos exactos del hardware. KQ2421 puede aparecer incluso tras esperar a VBlank, porque el compilador clasifica por el nombre del llamador.

Ejemplo de uso

c
m_wait();__oam_dma(); // Transfer all 256 bytes from page 02 at VBlank.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo dedica una fila rotulada a cada operación. SET y MOVE muestran flechas de 8×8 hacia la derecha en (144,16) y (144,32); TILE, un cuadrado verde en (144,48); ATTR, la flecha invertida en ambos ejes en (144,64). HIDE en (144,80) y CLEAR en (144,112) deben quedar vacíos. META muestra dos flechas en (136,96) y (144,96), la segunda reflejada horizontalmente. NEXT 07 indica el siguiente índice tras escribir las entradas 5 y 6. Los bordes son rojos, el cuerpo verde y los detalles de la punta azules. Se comparan 8.704 píxeles por imagen y los bytes OAM transferidos; no se ha probado en hardware físico.

TRANSFER muestra una flecha en (144,128). Este programa envía la página 02 directamente con __oam_dma, incluidos todos los campos preparados por las operaciones individuales de sprites.

__oam_dma: PAGE02
PAGE02

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:186

Notas de la declaración (original)
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

Transfiere 256 bytes de la página de memoria CPU seleccionada a la OAM del hardware, desde la entrada 0. Permite usar una página OAM preparada aparte sin copiarla antes a la página 02. No modifica el origen.

Sintaxis

c
void __oam_dma_page(u8 page);

Parámetros

page

Byte alto de la dirección CPU de origen: pase 4 para 0x0400–0x04FF, no 0x0400. Proporcione una página completa de 256 bytes legibles y mantenga mapeado el banco necesario. Evite páginas con registros de E/S, cuyas lecturas pueden tener efectos secundarios. No se valida el origen.

Valor devuelto

Ninguno (void).

Condiciones y límites

Llame durante VBlank o con el renderizado desactivado; ninguna función DMA espera a VBlank. Ambas ponen OAMADDR a cero antes de transferir. El hardware suele detener la CPU 513 o 514 ciclos; la actividad DMC puede alargarlo. KUROSAKI lo aproxima a 513 ciclos, por lo que verificar la imagen no confirma los tiempos exactos del hardware. KQ2421 puede aparecer incluso tras esperar a VBlank, porque el compilador clasifica por el nombre del llamador.

Ejemplo de uso

c
m_wait();__oam_dma_page(4); // The argument is a page number, not 0x0400.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo dedica una fila rotulada a cada operación. SET y MOVE muestran flechas de 8×8 hacia la derecha en (144,16) y (144,32); TILE, un cuadrado verde en (144,48); ATTR, la flecha invertida en ambos ejes en (144,64). HIDE en (144,80) y CLEAR en (144,112) deben quedar vacíos. META muestra dos flechas en (136,96) y (144,96), la segunda reflejada horizontalmente. NEXT 07 indica el siguiente índice tras escribir las entradas 5 y 6. Los bordes son rojos, el cuerpo verde y los detalles de la punta azules. Se comparan 8.704 píxeles por imagen y los bytes OAM transferidos; no se ha probado en hardware físico.

Esta variante copia la página 02 preparada a otro array en 0x0400 y cambia solo el mosaico de la entrada 7 a 129. __oam_dma_page(4) debe mostrar un cuadrado verde en (144,128), junto a TRANSFER, mientras la página 02 conserva el mosaico 128 de la flecha. Las demás filas coinciden con el ejemplo predeterminado. Se comparan los 256 bytes OAM con la página 04 y se comprueba que la página 02 sigue intacta.

__oam_dma_page: PAGE04
PAGE04

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:188

Notas de la declaración (original)
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

Conserva los bits 0–3 de la lectura NES suministrada (pad & 0x0F): A, B, Select y Start. Solo filtra un valor ya leído; no accede al hardware del mando.

Sintaxis

c
u8 __pad_buttons(u8 pad);

Parámetros

pad

Un valor NES de 8 bits ya leído, por ejemplo el resultado de __pad_read1_safe(). Pasa el valor, no un puntero, un número de puerto ni una máscara lógica input_*.

Valor devuelto

Máscara de 0 a 15 en el orden indicado. Puede tener varios bits activos; comprueba un botón con result & bit, no comparando todo el resultado con 1.

Condiciones y límites

Formato NES directo: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit a 1 significa pulsado. Usa estos valores con nes_pad_* y los resultados directos de __pad_*; no los confundas con las máscaras comunes BTN_* de input.h.

Ejemplo de uso

c
buttons=__pad_buttons(raw1); // Low nibble of 129: 1, the A button.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Este programa compara los dos puertos, las lecturas normales y con comprobación de coincidencia, los filtros y las macros abreviadas. Mantén A + derecha en el mando 1 y B + izquierda en el mando 2. RAW P1, SAFE P1 y ALIAS P1 valen 129; los tres valores P2 son 66. BUTTONS P1 vale 1 y DIRS P1 vale 128: el filtro de direcciones conserva las posiciones de los bits altos. FAILED CHECKS debe ser 0. Los valores capturados permanecen en pantalla.

__pad_buttons: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:213

Notas de la declaración (original)
Return pad & 0F for A/B/Select/Start; this masks a supplied snapshot and performs no polling.
__pad_dirs

Conserva los bits 4–7 de la lectura NES (pad & 0xF0): arriba, abajo, izquierda y derecha. Los bits mantienen su posición; solo derecha devuelve 128, no 8. No lee el mando.

Sintaxis

c
u8 __pad_dirs(u8 pad);

Parámetros

pad

Un valor NES de 8 bits ya leído, por ejemplo el resultado de __pad_read1_safe(). Pasa el valor, no un puntero, un número de puerto ni una máscara lógica input_*.

Valor devuelto

El valor suministrado AND 0xF0: 0, 16, 32, …, 240. Las direcciones permanecen en los bits 4–7.

Condiciones y límites

Formato NES directo: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit a 1 significa pulsado. Usa estos valores con nes_pad_* y los resultados directos de __pad_*; no los confundas con las máscaras comunes BTN_* de input.h.

Ejemplo de uso

c
directions=__pad_dirs(raw1); // High nibble retained in place: 128, not 8.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Este programa compara los dos puertos, las lecturas normales y con comprobación de coincidencia, los filtros y las macros abreviadas. Mantén A + derecha en el mando 1 y B + izquierda en el mando 2. RAW P1, SAFE P1 y ALIAS P1 valen 129; los tres valores P2 son 66. BUTTONS P1 vale 1 y DIRS P1 vale 128: el filtro de direcciones conserva las posiciones de los bits altos. FAILED CHECKS debe ser 0. Los valores capturados permanecen en pantalla.

__pad_dirs: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:215

Notas de la declaración (original)
Return pad & F0 in the original bit positions; this does not normalize directions to bits 0..3.
__pad_read1

Fija el estado de ambos mandos escribiendo 1 y después 0 en $4016, y lee los ocho bits serie D0 del mando 1 en $4016. Devuelve una lectura de botones mantenidos en formato NES, sin reintentos ni cálculo de transiciones.

Sintaxis

c
u8 __pad_read1(void);

Valor devuelto

Máscara de botones mantenidos de 8 bits en orden NES. Cada bit a 1 identifica un botón pulsado; 0 significa ninguno. No es un booleano normalizado.

Condiciones y límites

Formato NES directo: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit a 1 significa pulsado. Usa estos valores con nes_pad_* y los resultados directos de __pad_*; no los confundas con las máscaras comunes BTN_* de input.h.

Esta llamada obtiene una nueva lectura del hardware. No actualiza el estado guardado de input_* ni nes_pad1_*, ni genera eventos de pulsación, liberación o repetición. Guarda la lectura y compárala con tu máscara anterior cuando necesites detectar cambios.

Ejemplo de uso

c
raw1=__pad_read1(); // Controller 1: A (1) | Right (128) = 129.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Este programa compara los dos puertos, las lecturas normales y con comprobación de coincidencia, los filtros y las macros abreviadas. Mantén A + derecha en el mando 1 y B + izquierda en el mando 2. RAW P1, SAFE P1 y ALIAS P1 valen 129; los tres valores P2 son 66. BUTTONS P1 vale 1 y DIRS P1 vale 128: el filtro de direcciones conserva las posiciones de los bits altos. FAILED CHECKS debe ser 0. Los valores capturados permanecen en pantalla.

__pad_read1: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:205

Notas de la declaración (original)
Latch and sample eight D0 bits from port 1; bit order is A,B,Select,Start,Up,Down,Left,Right.
__pad_read1_d1

Lee ocho bits serie de la entrada de expansión D1 en $4016. Escribe 1 y después 0 en $4016 para fijar los estados, toma ocho muestras del bit 1 y las coloca, por orden, en los bits 0–7 del resultado. Es una sola lectura del canal D1 del puerto 1; no lee el canal D0 del mando normal ni reintenta para obtener muestras coincidentes.

Sintaxis

c
u8 __pad_read1_d1(void);

Valor devuelto

Máscara de botones mantenidos de 8 bits en orden NES. Cada bit a 1 identifica un botón pulsado; 0 significa ninguno. No es un booleano normalizado.

Condiciones y límites

Formato NES directo: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit a 1 significa pulsado. Usa estos valores con nes_pad_* y los resultados directos de __pad_*; no los confundas con las máscaras comunes BTN_* de input.h.

Usa un periférico que entregue una secuencia de mando serie de ocho bits compatible por la línea D1 elegida. La función no identifica el dispositivo ni implementa todos los protocolos de expansión. Cada llamada vuelve a fijar ambos flujos de control; termina una lectura completa antes de iniciar otra. Los resultados son lecturas nuevas y no actualizan las cachés input_* ni nes_pad1_*.

El modelo de mandos de KUROSAKI publicado solo devuelve D0 y el bit 6 fijo. Por ello, D1 y el bit del micrófono siempre son 0 en este emulador, incluso con botones normales pulsados. La ejecución incluida verifica la compilación, la entrada normal y esta observación de señal cero. No verifica señales periféricas distintas de cero, tiempos eléctricos ni compatibilidad real; un cero aquí no demuestra que el dispositivo funcione ni que estas funciones sean incapaces de leerlo en hardware.

Ejemplo de uso

c
d1_first=__pad_read1_d1();

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Este programa compara la entrada normal D0 con D1 y el nivel digital del micrófono. Mantén A en el mando 1 y B en el mando 2 hasta capturar la lectura. NORMAL P1 y NORMAL P2 muestran 001 y 002. En la ejecución KUROSAKI incluida, D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL y MIC DIRECT muestran 000 porque esas líneas no se emulan. Después, los valores quedan fijos. Con periféricos adecuados y sus líneas implementadas, los valores dependen de la entrada real; interprétalos según los bits descritos arriba en lugar de esperar estos ceros.

El modelo de mandos de KUROSAKI publicado solo devuelve D0 y el bit 6 fijo. Por ello, D1 y el bit del micrófono siempre son 0 en este emulador, incluso con botones normales pulsados. La ejecución incluida verifica la compilación, la entrada normal y esta observación de señal cero. No verifica señales periféricas distintas de cero, tiempos eléctricos ni compatibilidad real; un cero aquí no demuestra que el dispositivo funcione ni que estas funciones sean incapaces de leerlo en hardware.

__pad_read1_d1: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:223

Notas de la declaración (original)
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

Lee el mando 1 mediante pares de llamadas completas a __pad_read1(). Devuelve la máscara NES cuando las dos lecturas coinciden. El resultado es el estado mantenido, no eventos de nueva pulsación.

Sintaxis

c
u8 __pad_read1_safe(void);

Valor devuelto

Máscara de botones mantenidos de 8 bits en orden NES. Cada bit a 1 identifica un botón pulsado; 0 significa ninguno. No es un booleano normalizado.

Condiciones y límites

Formato NES directo: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit a 1 significa pulsado. Usa estos valores con nes_pad_* y los resultados directos de __pad_*; no los confundas con las máscaras comunes BTN_* de input.h.

Esta llamada obtiene una nueva lectura del hardware. No actualiza el estado guardado de input_* ni nes_pad1_*, ni genera eventos de pulsación, liberación o repetición. Guarda la lectura y compárala con tu máscara anterior cuando necesites detectar cambios.

La lectura segura repite pares de lecturas completas hasta obtener dos máscaras iguales. Cada lectura activa el strobe de $4016 y reinicia la secuencia serie de ambos mandos. No hay límite de reintentos. Esta comprobación no demuestra compatibilidad con todas las combinaciones de DMC y periféricos.

Ejemplo de uso

c
safe1=__pad_read1_safe(); // Repeat paired controller-1 reads until they agree.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Este programa compara los dos puertos, las lecturas normales y con comprobación de coincidencia, los filtros y las macros abreviadas. Mantén A + derecha en el mando 1 y B + izquierda en el mando 2. RAW P1, SAFE P1 y ALIAS P1 valen 129; los tres valores P2 son 66. BUTTONS P1 vale 1 y DIRS P1 vale 128: el filtro de direcciones conserva las posiciones de los bits altos. FAILED CHECKS debe ser 0. Los valores capturados permanecen en pantalla.

__pad_read1_safe: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:209

Notas de la declaración (original)
Repeat pairs of port-1 polls until both bytes match; the retry loop has no fixed limit.
__pad_read2

Fija el estado de ambos mandos escribiendo 1 y después 0 en $4016, y lee los ocho bits serie D0 del mando 2 en $4017. Devuelve una lectura de botones mantenidos en formato NES, sin reintentos ni cálculo de transiciones.

Sintaxis

c
u8 __pad_read2(void);

Valor devuelto

Máscara de botones mantenidos de 8 bits en orden NES. Cada bit a 1 identifica un botón pulsado; 0 significa ninguno. No es un booleano normalizado.

Condiciones y límites

Formato NES directo: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit a 1 significa pulsado. Usa estos valores con nes_pad_* y los resultados directos de __pad_*; no los confundas con las máscaras comunes BTN_* de input.h.

Esta llamada obtiene una nueva lectura del hardware. No actualiza el estado guardado de input_* ni nes_pad1_*, ni genera eventos de pulsación, liberación o repetición. Guarda la lectura y compárala con tu máscara anterior cuando necesites detectar cambios.

Ejemplo de uso

c
raw2=__pad_read2(); // Controller 2: B (2) | Left (64) = 66.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Este programa compara los dos puertos, las lecturas normales y con comprobación de coincidencia, los filtros y las macros abreviadas. Mantén A + derecha en el mando 1 y B + izquierda en el mando 2. RAW P1, SAFE P1 y ALIAS P1 valen 129; los tres valores P2 son 66. BUTTONS P1 vale 1 y DIRS P1 vale 128: el filtro de direcciones conserva las posiciones de los bits altos. FAILED CHECKS debe ser 0. Los valores capturados permanecen en pantalla.

__pad_read2: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:207

Notas de la declaración (original)
Latch and sample eight D0 bits from port 2 in the same NES button order.
__pad_read2_d1

Lee ocho bits serie de la entrada de expansión D1 en $4017. Escribe 1 y después 0 en $4016 para fijar los estados, toma ocho muestras del bit 1 y las coloca, por orden, en los bits 0–7 del resultado. Es una sola lectura del canal D1 del puerto 2; no lee el canal D0 del mando normal ni reintenta para obtener muestras coincidentes.

Sintaxis

c
u8 __pad_read2_d1(void);

Valor devuelto

Máscara de botones mantenidos de 8 bits en orden NES. Cada bit a 1 identifica un botón pulsado; 0 significa ninguno. No es un booleano normalizado.

Condiciones y límites

Formato NES directo: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit a 1 significa pulsado. Usa estos valores con nes_pad_* y los resultados directos de __pad_*; no los confundas con las máscaras comunes BTN_* de input.h.

Usa un periférico que entregue una secuencia de mando serie de ocho bits compatible por la línea D1 elegida. La función no identifica el dispositivo ni implementa todos los protocolos de expansión. Cada llamada vuelve a fijar ambos flujos de control; termina una lectura completa antes de iniciar otra. Los resultados son lecturas nuevas y no actualizan las cachés input_* ni nes_pad1_*.

El modelo de mandos de KUROSAKI publicado solo devuelve D0 y el bit 6 fijo. Por ello, D1 y el bit del micrófono siempre son 0 en este emulador, incluso con botones normales pulsados. La ejecución incluida verifica la compilación, la entrada normal y esta observación de señal cero. No verifica señales periféricas distintas de cero, tiempos eléctricos ni compatibilidad real; un cero aquí no demuestra que el dispositivo funcione ni que estas funciones sean incapaces de leerlo en hardware.

Ejemplo de uso

c
d1_second=__pad_read2_d1();

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Este programa compara la entrada normal D0 con D1 y el nivel digital del micrófono. Mantén A en el mando 1 y B en el mando 2 hasta capturar la lectura. NORMAL P1 y NORMAL P2 muestran 001 y 002. En la ejecución KUROSAKI incluida, D1 PORT 1, D1 PORT 2, EXP ALIAS 1, EXP ALIAS 2, MIC LEVEL y MIC DIRECT muestran 000 porque esas líneas no se emulan. Después, los valores quedan fijos. Con periféricos adecuados y sus líneas implementadas, los valores dependen de la entrada real; interprétalos según los bits descritos arriba en lugar de esperar estos ceros.

El modelo de mandos de KUROSAKI publicado solo devuelve D0 y el bit 6 fijo. Por ello, D1 y el bit del micrófono siempre son 0 en este emulador, incluso con botones normales pulsados. La ejecución incluida verifica la compilación, la entrada normal y esta observación de señal cero. No verifica señales periféricas distintas de cero, tiempos eléctricos ni compatibilidad real; un cero aquí no demuestra que el dispositivo funcione ni que estas funciones sean incapaces de leerlo en hardware.

__pad_read2_d1: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:225

Notas de la declaración (original)
Latch and read an eight-button stream from port-2 data line D1 rather than D0.
__pad_read2_safe

Lee el mando 2 mediante pares de llamadas completas a __pad_read2(). Devuelve la máscara NES cuando las dos lecturas coinciden. El resultado es el estado mantenido, no eventos de nueva pulsación.

Sintaxis

c
u8 __pad_read2_safe(void);

Valor devuelto

Máscara de botones mantenidos de 8 bits en orden NES. Cada bit a 1 identifica un botón pulsado; 0 significa ninguno. No es un booleano normalizado.

Condiciones y límites

Formato NES directo: A=0x01, B=0x02, SELECT=0x04, START=0x08, UP=0x10, DOWN=0x20, LEFT=0x40, RIGHT=0x80. Un bit a 1 significa pulsado. Usa estos valores con nes_pad_* y los resultados directos de __pad_*; no los confundas con las máscaras comunes BTN_* de input.h.

Esta llamada obtiene una nueva lectura del hardware. No actualiza el estado guardado de input_* ni nes_pad1_*, ni genera eventos de pulsación, liberación o repetición. Guarda la lectura y compárala con tu máscara anterior cuando necesites detectar cambios.

La lectura segura repite pares de lecturas completas hasta obtener dos máscaras iguales. Cada lectura activa el strobe de $4016 y reinicia la secuencia serie de ambos mandos. No hay límite de reintentos. Esta comprobación no demuestra compatibilidad con todas las combinaciones de DMC y periféricos.

Ejemplo de uso

c
safe2=__pad_read2_safe(); // The same consistency check on controller 2.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Este programa compara los dos puertos, las lecturas normales y con comprobación de coincidencia, los filtros y las macros abreviadas. Mantén A + derecha en el mando 1 y B + izquierda en el mando 2. RAW P1, SAFE P1 y ALIAS P1 valen 129; los tres valores P2 son 66. BUTTONS P1 vale 1 y DIRS P1 vale 128: el filtro de direcciones conserva las posiciones de los bits altos. FAILED CHECKS debe ser 0. Los valores capturados permanecen en pantalla.

__pad_read2_safe: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:211

Notas de la declaración (original)
Repeat pairs of port-2 polls until both bytes match; this does not populate pad.c globals.
__palette_bg_load

Reinicia el latch PPU, fija 0x3F00 y escribe exactamente 16 bytes del origen mediante PPUDATA para las cuatro paletas de fondo. La transferencia es inmediata; no cambia la selección de paleta de los atributos ni carga gráficos de tiles.

Sintaxis

c
void __palette_bg_load(const u8* pal16);

Parámetros

pal16

Puntero CPU a 16 bytes legibles de códigos de paleta NES, no ternas RGB. Mantenga el banco de origen mapeado hasta el retorno síncrono; no cambia bancos ni comprueba la longitud. Use incremento PPU 1 para alcanzar las 16 direcciones previstas.

Valor devuelto

Ninguno (void).

Condiciones y límites

Las direcciones PPU 0x3F10/14/18/1C son alias de 0x3F00/04/08/0C. Cargar la paleta de sprites también escribe esas entradas de fondo. El compilador envía los bytes sin modificarlos; no valida colores, combina paletas ni conserva entradas compartidas.

El acceso directo a PPU requiere desactivar el renderizado o completar toda la operación dentro de un VBlank seguro. Estas funciones no esperan, encolan, bloquean NMI ni restauran el desplazamiento. Evite que una interrupción cambie el latch o la dirección compartidos durante la llamada. El bit 2 de PPUCTRL selecciona un incremento de destino de 1 o 32 por byte. La PPU gestiona los espejos y el retorno de direcciones; no se puede escribir en CHR-ROM. Restablezca el desplazamiento deseado antes de renderizar.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

c
__palette_bg_load(scene_palette); // Four background palettes, 16 bytes total.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__palette_bg_load: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:138

Notas de la declaración (original)
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

Reinicia el latch PPU, fija 0x3F10 y escribe exactamente 16 bytes del origen mediante PPUDATA para las cuatro paletas de sprites. Los bits de atributo del sprite eligen la paleta visible. No modifica OAM ni transfiere gráficos de tiles de sprites.

Sintaxis

c
void __palette_sp_load(const u8* pal16);

Parámetros

pal16

Puntero CPU a 16 bytes legibles de códigos de paleta NES, no ternas RGB. Mantenga el banco de origen mapeado hasta el retorno síncrono; no cambia bancos ni comprueba la longitud. Use incremento PPU 1 para alcanzar las 16 direcciones previstas.

Valor devuelto

Ninguno (void).

Condiciones y límites

Las direcciones PPU 0x3F10/14/18/1C son alias de 0x3F00/04/08/0C. Cargar la paleta de sprites también escribe esas entradas de fondo. El compilador envía los bytes sin modificarlos; no valida colores, combina paletas ni conserva entradas compartidas.

El acceso directo a PPU requiere desactivar el renderizado o completar toda la operación dentro de un VBlank seguro. Estas funciones no esperan, encolan, bloquean NMI ni restauran el desplazamiento. Evite que una interrupción cambie el latch o la dirección compartidos durante la llamada. El bit 2 de PPUCTRL selecciona un incremento de destino de 1 o 32 por byte. La PPU gestiona los espejos y el retorno de direcciones; no se puede escribir en CHR-ROM. Restablezca el desplazamiento deseado antes de renderizar.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

c
__palette_sp_load(scene_palette); // Four sprite palettes, 16 bytes total.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__palette_sp_load: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:140

Notas de la declaración (original)
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

Evalúa ppu_addr una vez, lee PPUSTATUS para reiniciar el latch compartido y escribe los bytes alto y bajo en PPUADDR. Prepara el destino de accesos posteriores a PPUDATA sin escribir datos. La PPU aplica sus máscaras y espejos de dirección.

Sintaxis

c
void __ppu_addr(u16 ppu_addr);

Parámetros

ppu_addr

Dirección PPU en bytes (0x0000–0x3FFF), no un puntero de CPU. El destino debe admitir escritura: modificar patrones requiere CHR RAM, no CHR ROM. Se aplican el mapeo y el mirroring de la PPU. Para copias y rellenos contiguos, selecciona incremento 1 en PPUCTRL. Ejecuta en un periodo seguro y restablece después el desplazamiento; el ejecutor escribe PPUADDR y PPUDATA sin restaurarlo.

Valor devuelto

Ninguno (void).

Condiciones y límites

El acceso directo a PPU requiere desactivar el renderizado o completar toda la operación dentro de un VBlank seguro. Estas funciones no esperan, encolan, bloquean NMI ni restauran el desplazamiento. Evite que una interrupción cambie el latch o la dirección compartidos durante la llamada. El bit 2 de PPUCTRL selecciona un incremento de destino de 1 o 32 por byte. La PPU gestiona los espejos y el retorno de direcciones; no se puede escribir en CHR-ROM. Restablezca el desplazamiento deseado antes de renderizar.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

c
address_port=0x21;
__ppu_addr(0x2192); // Reset latch and set the cell at tile (18,12).

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__ppu_addr: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:106

Notas de la declaración (original)
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

Devuelve la copia de PPUCTRL mantenida por software. Úsala para guardar la activación de NMI, la selección de tabla de patrones y el incremento de dirección antes de un cambio temporal.

Sintaxis

c
u8 __ppu_ctrl_get(void);

Valor devuelto

Devuelve como u8 el último valor de control mantenido por la biblioteca o las funciones intrínsecas de ajuste. No lee PPUSTATUS.

Condiciones y límites

PPUCTRL y PPUMASK son de solo escritura. Esta función no lee el hardware. Las escrituras directas en $2000/$2001 o el ensamblador personalizado no actualizan las copias. Usa siempre las operaciones __ppu_*_set correspondientes. Leer la copia no cambia el latch ni el estado de las interrupciones.

Ejemplo de uso

c
__ppu_ctrl_set(4);
    if(__ppu_ctrl_get()!=4)failures=1; // Inspect the software shadow, not PPUSTATUS.
    __ppu_ctrl_set(0);

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

La alternativa prepara la pantalla sin renderizado, instala paletas, borra tiles y atributos y dibuja la prueba. Un patrón azul de tiles sólidos, barras de media anchura y triángulos comienza en (0,16); un bloque verde sólido, en (0,96). Cada región mide 256×64 píxeles, salvo la última celda de 8×8 en (248,72) o (248,152), que queda negra porque se transfieren 255 bytes de tiles. En y=208 aparecen un cuadrado rojo de 8×8 en x=16, una barra izquierda de 4×8 en x=224 y un triángulo de ocho filas en x=240. FAILED CHECKS indica 0. Pruebas de estado separadas comprueban ambos registros de control, los 32 bytes de paleta y los 1.024 bytes borrados de la tabla de nombres. Las cuatro llamadas declaradas deben fallar al enlazar.

__ppu_ctrl_get: Otros usos
Otros usos

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:103

Notas de la declaración (original)
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

Sustituye PPUCTRL y su copia de software por el byte indicado. Controla NMI, incremento de dirección PPU, tablas de patrones, tamaño de sprites y tabla de nombres base. No cambia PPUMASK ni conserva los bits no especificados.

Sintaxis

c
void __ppu_ctrl_set(u8 value);

Parámetros

value

Byte sin signo 0..255, escrito tal como se recibe. Es un byte completo de registro o datos, no un booleano ni un número de bit.

Valor devuelto

Ninguno (void).

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

c
__ppu_ctrl_set(0x80); // NMI enabled, increment 1, nametable/pattern tables 0.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__ppu_ctrl_set: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:100

Notas de la declaración (original)
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

Escribe value una vez en PPUDATA, en la dirección PPU actual. Después, la dirección avanza 1 o 32 según PPUCTRL. No establece la dirección, reinicia el latch ni encola la transferencia.

Sintaxis

c
void __ppu_data(u8 value);

Parámetros

value

Byte sin signo 0..255, escrito tal como se recibe. Es un byte completo de registro o datos, no un booleano ni un número de bit.

Valor devuelto

Ninguno (void).

Condiciones y límites

El acceso directo a PPU requiere desactivar el renderizado o completar toda la operación dentro de un VBlank seguro. Estas funciones no esperan, encolan, bloquean NMI ni restauran el desplazamiento. Evite que una interrupción cambie el latch o la dirección compartidos durante la llamada. El bit 2 de PPUCTRL selecciona un incremento de destino de 1 o 32 por byte. La PPU gestiona los espejos y el retorno de direcciones; no se puede escribir en CHR-ROM. Restablezca el desplazamiento deseado antes de renderizar.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

c
__ppu_data(3); // Green triangle at (144,96); destination advances by one.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__ppu_data: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:108

Notas de la declaración (original)
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

Devuelve la copia de PPUMASK mantenida por software. Úsala para conservar los ajustes de renderizado, visibilidad del borde izquierdo, escala de grises y énfasis de color.

Sintaxis

c
u8 __ppu_mask_get(void);

Valor devuelto

Devuelve como u8 el último valor de control mantenido por la biblioteca o las funciones intrínsecas de ajuste. No lee PPUSTATUS.

Condiciones y límites

PPUCTRL y PPUMASK son de solo escritura. Esta función no lee el hardware. Las escrituras directas en $2000/$2001 o el ensamblador personalizado no actualizan las copias. Usa siempre las operaciones __ppu_*_set correspondientes. Leer la copia no cambia el latch ni el estado de las interrupciones.

Ejemplo de uso

c
__ppu_mask_set(0xE7);
    if(__ppu_mask_get()!=0xE7)failures=1;
    __ppu_mask_set(0);

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

La alternativa prepara la pantalla sin renderizado, instala paletas, borra tiles y atributos y dibuja la prueba. Un patrón azul de tiles sólidos, barras de media anchura y triángulos comienza en (0,16); un bloque verde sólido, en (0,96). Cada región mide 256×64 píxeles, salvo la última celda de 8×8 en (248,72) o (248,152), que queda negra porque se transfieren 255 bytes de tiles. En y=208 aparecen un cuadrado rojo de 8×8 en x=16, una barra izquierda de 4×8 en x=224 y un triángulo de ocho filas en x=240. FAILED CHECKS indica 0. Pruebas de estado separadas comprueban ambos registros de control, los 32 bytes de paleta y los 1.024 bytes borrados de la tabla de nombres. Las cuatro llamadas declaradas deben fallar al enlazar.

__ppu_mask_get: Otros usos
Otros usos

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

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

Sustituye todo el byte PPUMASK y su copia de software por value. Seleccione explícitamente renderizado, borde izquierdo, escala de grises y énfasis. No combina bits con la máscara anterior ni modifica PPUCTRL.

Sintaxis

c
void __ppu_mask_set(u8 value);

Parámetros

value

Byte sin signo 0..255, escrito tal como se recibe. Es un byte completo de registro o datos, no un booleano ni un número de bit.

Valor devuelto

Ninguno (void).

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

c
__ppu_mask_set(0x0A); // Background enabled, including its leftmost eight pixels.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__ppu_mask_set: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:98

Notas de la declaración (original)
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

Escribe cero en PPUMASK y su copia de software, desactivando el fondo y los sprites. Borra todos los bits, incluidos escala de grises y énfasis. PPUCTRL y la activación de NMI no cambian; controle NMI por separado al preparar transferencias compartidas.

Sintaxis

c
void __ppu_off(void);

Valor devuelto

Ninguno (void).

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

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.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__ppu_off: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:96

Notas de la declaración (original)
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

Sustituye PPUMASK y su copia de software por 0x1E, activando fondo y sprites, incluidos los ocho píxeles del borde izquierdo. Sobrescribe las opciones de escala de grises y énfasis de color; no cambia PPUCTRL ni activa NMI.

Sintaxis

c
void __ppu_on(void);

Valor devuelto

Ninguno (void).

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

c
__ppu_on(); // Replace mask with 0x1E: enable sprites and background at both edges.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__ppu_on: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:94

Notas de la declaración (original)
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

Lee PPUSTATUS una vez y devuelve el byte observado antes de que la lectura borre la bandera VBlank. También reinicia el latch compartido de desplazamiento y dirección. Es una lectura de hardware con efectos secundarios, no un contador de fotogramas guardado ni una prueba de finalización de NMI.

Sintaxis

c
u8 __ppu_read_status(void);

Valor devuelto

Byte de estado bruto. El bit 7 indica VBlank, el 6 colisión con sprite cero y el 5 desbordamiento de sprites. Compruebe la bandera con una máscara; no trate todo el byte como booleano o número de fotograma. Los bits inferiores no son un estado de aplicación portátil.

Condiciones y límites

Leer PPUSTATUS puede afectar la temporización de NMI y las siguientes escrituras a PPUSCROLL y PPUADDR. No lo inserte entre los dos bytes de otro procedimiento. Estas llamadas no esperan VBlank ni completan colas de transferencia pendientes.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

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).

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__ppu_read_status: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:110

Notas de la declaración (original)
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

Realiza la misma selección de banco PRG adaptada al mapper que __bankswitch. Comparte sus comprobaciones del banco y no restaura automáticamente la asignación anterior.

Sintaxis

c
void __prg_bank_set(u8 bank);

Parámetros

bank

Número de banco ROM de 8 bits sin signo. En una llamada debe coincidir con la ubicación de la función; en una lectura selecciona el origen. Usa el intervalo válido para el cartucho.

Valor devuelto

Ninguno (void).

Condiciones y límites

Un cambio de banco persistente no debe retirar el código en ejecución ni su dirección de retorno. Coloca esta llamada, la propia bank_switch cuando se use y el código posterior en el banco común o fijo 0, como hace el ejemplo.

El registro de la biblioteca empieza en cero y no detecta el mapa inicial del cartucho. Inicialízalo con bank_switch. Las intrínsecas y las escrituras directas al mapper no actualizan este registro separado; bank_get_current no permite verificar el mapa real del hardware.

Los bancos FC son números lógicos del compilador. Cero designa ROM común, no un destino de cambio; la rutina lo convierte en banco 1. SUROM512 admite 1–30: una constante fuera de rango pasada directamente a la intrínseca de cambio produce un error de compilación; un valor inválido en ejecución selecciona 1 y activa el estado de fallo del mapper. Lee la ROM común directamente, sin pedir un cambio al banco cero.

Ejemplo de uso

c
__prg_bank_set(1);bank_expect(home1[0]==0xA1,21);
__prg_bank_set(2); // Same mapper helper as __bankswitch.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

bank_switch(2) registra y selecciona inicialmente el banco 2. Después, el cambio directo selecciona el banco 1 y lee A1 de home1 mientras bank_get_current sigue indicando 2; al volver, home2 devuelve B2 otra vez.

El programa completo enseña a identificar datos con un banco y una dirección cercana, a llamar callbacks entre bancos y a distinguir el registro de la biblioteca del mapa real. Todos los números se muestran en hexadecimal. BANK IN y BANK OUT deben ser 02; BYTE HEX, D3; WORD HEX, 5C7A; FAILED CHECKS y FIRST FAILURE, 00. CGB y FC muestran texto azul; DMG, negro.

CALL COUNT muestra 02: la macro far_call y __farcall invocan el callback una vez cada uno.

__prg_bank_set: MMC3
MMC3

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:81

Notas de la declaración (original)
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

Avanza una vez el LFSR Galois FC compartido de 16 bits y devuelve el XOR de los bytes bajo y alto actualizados. Un LFSR es un registro de desplazamiento cuya realimentación se calcula con XOR; una semilla fija produce, por tanto, una secuencia de bytes reproducible.

Sintaxis

c
u8 __rng8(void);

Valor devuelto

Un resultado u8 (0–255) tras avanzar una vez el generador compartido. Esto describe el tipo y los límites del valor, no que todos los bytes tengan la misma probabilidad.

Condiciones y límites

El paso FC desplaza el estado de 16 bits un bit a la derecha. Si el bit menos significativo original era 1, aplica XOR con 0xB400 al estado desplazado. El byte devuelto es low_byte XOR high_byte, no el byte bajo sin modificar.

Si ambos bytes de estado son cero, la siguiente extracción los sustituye por 0xA55A antes del desplazamiento. Así, __rng_seed(0) seguido de __rng8() devuelve 255. La llamada de semilla guarda cero; la sustitución ocurre al extraer.

Estas llamadas comparten estado persistente. Para reproducir la secuencia hacen falta el mismo destino, semilla y orden de llamadas, incluidas las de otras funciones auxiliares. Mantén la semilla y las extracciones en un único contexto de ejecución; estas comprobaciones no demuestran que sea seguro usarlas simultáneamente desde una interrupción.

GB y FC usan algoritmos distintos y no prometen la misma secuencia con la misma semilla. Son generadores deterministas para juegos, no fuentes de aleatoriedad criptográfica; las pruebas comprueban el comportamiento de la implementación, no su calidad estadística ni su período.

Ejemplo de uso

c
__rng_seed(0x1234);
first=__rng8();second=__rng8();third=__rng8(); // FC sequence: 19, 137, 240.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Este ejemplo lee tres bytes FC consecutivos. La semilla 0x1234 produce FIRST 00019, SECOND 00137 y THIRD 00240. Los valores muestran el orden de llamadas; no son frecuencias ni una puntuación de aleatoriedad.

El programa NROM completo muestra resultados decimales azules sobre negro. FAILED debe indicar 00000. El texto se transfiere con el renderizado desactivado antes de habilitar la pantalla de resultados.

La verificación comparó por caso 32 salidas y ocho extracciones de byte posteriores con un modelo independiente en el equipo anfitrión: 93 casos GB y 18 FC, incluidos semillas constantes o en tiempo de ejecución y límites GB de rango, probabilidad y pesos. Las pantallas didácticas se comprobaron tras 240 fotogramas del emulador. No se probaron hardware físico ni calidad estadística.

__rng8: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:39

Notas de la declaración (original)
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

Sustituye los dos bytes de estado del generador FC. Reutilizar una semilla permite que una prueba o repetición empiece en el mismo punto de su secuencia pseudoaleatoria; la llamada no avanza el generador.

Sintaxis

c
void __rng_seed(u16 seed);

Parámetros

seed

Semilla de 16 bits sin signo, de 0 a 65535. Los dos bytes se almacenan tal como se reciben. Elige y registra la semilla antes de la secuencia de extracciones del juego que quieras reproducir.

Valor devuelto

Ninguno (void).

Condiciones y límites

Si ambos bytes de estado son cero, la siguiente extracción los sustituye por 0xA55A antes del desplazamiento. Así, __rng_seed(0) seguido de __rng8() devuelve 255. La llamada de semilla guarda cero; la sustitución ocurre al extraer.

Estas llamadas comparten estado persistente. Para reproducir la secuencia hacen falta el mismo destino, semilla y orden de llamadas, incluidas las de otras funciones auxiliares. Mantén la semilla y las extracciones en un único contexto de ejecución; estas comprobaciones no demuestran que sea seguro usarlas simultáneamente desde una interrupción.

GB y FC usan algoritmos distintos y no prometen la misma secuencia con la misma semilla. Son generadores deterministas para juegos, no fuentes de aleatoriedad criptográfica; las pruebas comprueban el comportamiento de la implementación, no su calidad estadística ni su período.

Ejemplo de uso

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.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Este ejemplo muestra un inicio FC reproducible y la regla de semilla cero. FIRST y RESEEDED muestran 00019 con semilla 0x1234. ZERO SEED muestra 00255 después de sembrar con cero.

El programa NROM completo muestra resultados decimales azules sobre negro. FAILED debe indicar 00000. El texto se transfiere con el renderizado desactivado antes de habilitar la pantalla de resultados.

La verificación comparó por caso 32 salidas y ocho extracciones de byte posteriores con un modelo independiente en el equipo anfitrión: 93 casos GB y 18 FC, incluidos semillas constantes o en tiempo de ejecución y límites GB de rango, probabilidad y pesos. Las pantallas didácticas se comprobaron tras 240 fotogramas del emulador. No se probaron hardware físico ni calidad estadística.

__rng_seed: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:37

Notas de la declaración (original)
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

Selecciona la máscara de renderizado encendida o apagada y espera un cambio del contador NMI. La máscara seleccionada sigue aplicada al retornar.

Sintaxis

c
void __rob_flash(u8 bright);

Parámetros

bright

Cero selecciona apagado; cualquier byte distinto de cero, incluido 255, selecciona encendido. No es un nivel de brillo.

Valor devuelto

Sin valor de retorno. Vuelve al código que llama cuando terminan las esperas solicitadas.

Condiciones y límites

Incluye intrinsics.h. El manejador NMI predeterminado debe estar funcionando y avanzando su contador. Si NMI está deshabilitado o un manejador personalizado no mantiene ese contador, la espera queda bloqueada. No hay tiempo de espera máximo.

La fase encendida pone PPUMASK y su copia de software en 0x1E; la apagada pone ambos a cero. No restaura la máscara anterior. Cambia conjuntamente el renderizado del fondo, sprites y borde izquierdo, y sustituye los ajustes de escala de grises y énfasis de color.

Habilitar el renderizado no crea por sí solo una pantalla blanca. Prepara tiles y entradas de paleta claros, y un color de fondo oscuro para la fase apagada. El ejemplo usa tiles blancos sólidos y fondo negro para hacer parpadear toda la pantalla durante la operación.

Las esperas cuentan cambios del contador NMI. El intervalo hasta el primer NMI puede ser menor que un fotograma. Llama desde el código principal, no desde una interrupción. Otra rutina que cambie la máscara de renderizado puede alterar el patrón.

Son primitivas de máscara de visualización y espera. No generan un preámbulo de comando de robot, una secuencia específica de dispositivo ni una confirmación de recepción. La verificación cubre escrituras PPUMASK, cuentas NMI y píxeles en KUROSAKI; no se ha probado la recepción de un robot físico.

Ejemplo de uso

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.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Tras ejecutarse, la pantalla muestra FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 y A5 BYTE WAITS 48. El diagrama representa 0xA5: 1,0,1,0 arriba y 0,1,0,1 abajo. Cada celda equivale a una espera; blanco significa renderizado activo y negro, desactivado. Un 1 tiene cuatro celdas blancas y dos negras; un 0, dos blancas y cuatro negras. FINAL MASK 0 y TIMING CHECKS OK indican que ha terminado.

__rob_flash: NTSC — Tras ejecutarse, la pantalla muestra FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 y A5 BYTE WAITS 48. El diagrama representa 0xA5: 1,0,1,0 arriba y 0,1,0,1 abajo. Cada celda equivale a una espera; blanco significa renderizado activo y negro, desactivado. Un 1 tiene cuatro celdas blancas y dos negras; un 0, dos blancas y cuatro negras. FINAL MASK 0 y TIMING CHECKS OK indican que ha terminado.
NTSC — Tras ejecutarse, la pantalla muestra FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 y A5 BYTE WAITS 48. El diagrama representa 0xA5: 1,0,1,0 arriba y 0,1,0,1 abajo. Cada celda equivale a una espera; blanco significa renderizado activo y negro, desactivado. Un 1 tiene cuatro celdas blancas y dos negras; un 0, dos blancas y cuatro negras. FINAL MASK 0 y TIMING CHECKS OK indican que ha terminado.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:272

Notas de la declaración (original)
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

Realiza las esperas encendidas solicitadas y después las apagadas para producir un pulso.

Sintaxis

c
void __rob_pulse(u8 on_frames, u8 off_frames);

Parámetros

on_frames

Número de esperas de la fase encendida, de 0 a 255. Cero omite esa fase.

off_frames

Número de esperas de la fase apagada, de 0 a 255. Cero omite esa fase. El total puede llegar a 510 esperas.

Valor devuelto

Sin valor de retorno. Vuelve al código que llama cuando terminan las esperas solicitadas.

Condiciones y límites

Si ambos contadores son cero, no cambian ni la máscara ni los tiempos. Cualquier espera apagada deja la máscara en cero; un pulso solo encendido deja 0x1E.

Incluye intrinsics.h. El manejador NMI predeterminado debe estar funcionando y avanzando su contador. Si NMI está deshabilitado o un manejador personalizado no mantiene ese contador, la espera queda bloqueada. No hay tiempo de espera máximo.

La fase encendida pone PPUMASK y su copia de software en 0x1E; la apagada pone ambos a cero. No restaura la máscara anterior. Cambia conjuntamente el renderizado del fondo, sprites y borde izquierdo, y sustituye los ajustes de escala de grises y énfasis de color.

Habilitar el renderizado no crea por sí solo una pantalla blanca. Prepara tiles y entradas de paleta claros, y un color de fondo oscuro para la fase apagada. El ejemplo usa tiles blancos sólidos y fondo negro para hacer parpadear toda la pantalla durante la operación.

Las esperas cuentan cambios del contador NMI. El intervalo hasta el primer NMI puede ser menor que un fotograma. Llama desde el código principal, no desde una interrupción. Otra rutina que cambie la máscara de renderizado puede alterar el patrón.

Son primitivas de máscara de visualización y espera. No generan un preámbulo de comando de robot, una secuencia específica de dispositivo ni una confirmación de recepción. La verificación cubre escrituras PPUMASK, cuentas NMI y píxeles en KUROSAKI; no se ha probado la recepción de un robot físico.

Ejemplo de uso

c
start=__nmi_ready();
    __rob_pulse(3,2); // Three enabled waits, then two disabled waits.
    rob_result[1]=(u8)(__nmi_ready()-start);

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Tras ejecutarse, la pantalla muestra FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 y A5 BYTE WAITS 48. El diagrama representa 0xA5: 1,0,1,0 arriba y 0,1,0,1 abajo. Cada celda equivale a una espera; blanco significa renderizado activo y negro, desactivado. Un 1 tiene cuatro celdas blancas y dos negras; un 0, dos blancas y cuatro negras. FINAL MASK 0 y TIMING CHECKS OK indican que ha terminado.

__rob_pulse: NTSC — Tras ejecutarse, la pantalla muestra FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 y A5 BYTE WAITS 48. El diagrama representa 0xA5: 1,0,1,0 arriba y 0,1,0,1 abajo. Cada celda equivale a una espera; blanco significa renderizado activo y negro, desactivado. Un 1 tiene cuatro celdas blancas y dos negras; un 0, dos blancas y cuatro negras. FINAL MASK 0 y TIMING CHECKS OK indican que ha terminado.
NTSC — Tras ejecutarse, la pantalla muestra FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 y A5 BYTE WAITS 48. El diagrama representa 0xA5: 1,0,1,0 arriba y 0,1,0,1 abajo. Cada celda equivale a una espera; blanco significa renderizado activo y negro, desactivado. Un 1 tiene cuatro celdas blancas y dos negras; un 0, dos blancas y cuatro negras. FINAL MASK 0 y TIMING CHECKS OK indican que ha terminado.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:274

Notas de la declaración (original)
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

Codifica un byte en ocho patrones de parpadeo, empezando por el bit más significativo. Un 1 usa cuatro esperas encendidas y dos apagadas; un 0, dos encendidas y cuatro apagadas. El total es de 48 esperas NMI.

Sintaxis

c
void __rob_send_byte(u8 pattern);

Parámetros

pattern

Patrón de ocho bits, de 0 a 255. Para 0xA5, el orden es 1,0,1,0,0,1,0,1. No es un ID de canal ni un número de acción del robot.

Valor devuelto

Sin valor de retorno. Vuelve al código que llama cuando terminan las esperas solicitadas.

Condiciones y límites

El último bit también termina con esperas apagadas, dejando PPUMASK y su copia en cero. Envía exactamente ocho bits, sin añadir marcas de inicio ni de fin.

Incluye intrinsics.h. El manejador NMI predeterminado debe estar funcionando y avanzando su contador. Si NMI está deshabilitado o un manejador personalizado no mantiene ese contador, la espera queda bloqueada. No hay tiempo de espera máximo.

La fase encendida pone PPUMASK y su copia de software en 0x1E; la apagada pone ambos a cero. No restaura la máscara anterior. Cambia conjuntamente el renderizado del fondo, sprites y borde izquierdo, y sustituye los ajustes de escala de grises y énfasis de color.

Habilitar el renderizado no crea por sí solo una pantalla blanca. Prepara tiles y entradas de paleta claros, y un color de fondo oscuro para la fase apagada. El ejemplo usa tiles blancos sólidos y fondo negro para hacer parpadear toda la pantalla durante la operación.

Las esperas cuentan cambios del contador NMI. El intervalo hasta el primer NMI puede ser menor que un fotograma. Llama desde el código principal, no desde una interrupción. Otra rutina que cambie la máscara de renderizado puede alterar el patrón.

Son primitivas de máscara de visualización y espera. No generan un preámbulo de comando de robot, una secuencia específica de dispositivo ni una confirmación de recepción. La verificación cubre escrituras PPUMASK, cuentas NMI y píxeles en KUROSAKI; no se ha probado la recepción de un robot físico.

Ejemplo de uso

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.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Tras ejecutarse, la pantalla muestra FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 y A5 BYTE WAITS 48. El diagrama representa 0xA5: 1,0,1,0 arriba y 0,1,0,1 abajo. Cada celda equivale a una espera; blanco significa renderizado activo y negro, desactivado. Un 1 tiene cuatro celdas blancas y dos negras; un 0, dos blancas y cuatro negras. FINAL MASK 0 y TIMING CHECKS OK indican que ha terminado.

__rob_send_byte: NTSC — Tras ejecutarse, la pantalla muestra FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 y A5 BYTE WAITS 48. El diagrama representa 0xA5: 1,0,1,0 arriba y 0,1,0,1 abajo. Cada celda equivale a una espera; blanco significa renderizado activo y negro, desactivado. Un 1 tiene cuatro celdas blancas y dos negras; un 0, dos blancas y cuatro negras. FINAL MASK 0 y TIMING CHECKS OK indican que ha terminado.
NTSC — Tras ejecutarse, la pantalla muestra FLASH WAITS 1, PULSE 3 ON / 2 OFF = 5 y A5 BYTE WAITS 48. El diagrama representa 0xA5: 1,0,1,0 arriba y 0,1,0,1 abajo. Cada celda equivale a una espera; blanco significa renderizado activo y negro, desactivado. Un 1 tiene cuatro celdas blancas y dos negras; un 0, dos blancas y cuatro negras. FINAL MASK 0 y TIMING CHECKS OK indican que ha terminado.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:278

Notas de la declaración (original)
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

Lee PPUSTATUS y descarta el resultado para reiniciar el latch compartido de desplazamiento y dirección. También borra la bandera VBlank. No escribe coordenadas de desplazamiento cero ni modifica los valores X/Y guardados.

Sintaxis

c
void __scroll_latch_reset(void);

Valor devuelto

Ninguno (void).

Condiciones y límites

Leer PPUSTATUS puede afectar la temporización de NMI y las siguientes escrituras a PPUSCROLL y PPUADDR. No lo inserte entre los dos bytes de otro procedimiento. Estas llamadas no esperan VBlank ni completan colas de transferencia pendientes.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

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.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__scroll_latch_reset: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:112

Notas de la declaración (original)
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

Guarda ambos desplazamientos en píxeles en las copias X/Y del runtime, lee PPUSTATUS para reiniciar el latch y escribe X e Y en PPUSCROLL, en ese orden. Actualiza el desplazamiento directamente; no encola movimientos de cámara, espera a NMI ni elige una tabla de nombres base.

Sintaxis

c
void __scroll_set(u8 x, u8 y);

Parámetros

x / y

Los desplazamientos son bytes sin signo medidos en píxeles. Use X 0..255 y normalmente Y 0..239; Y 240..255 se escribe sin cambios, pero tiene un comportamiento especial de filas PPU. PPUCTRL aporta los bits de tabla base. Restaure el desplazamiento tras el acceso directo a VRAM y antes de renderizar; la función no programa ese momento.

Valor devuelto

Ninguno (void).

Condiciones y límites

Leer PPUSTATUS puede afectar la temporización de NMI y las siguientes escrituras a PPUSCROLL y PPUADDR. No lo inserte entre los dos bytes de otro procedimiento. Estas llamadas no esperan VBlank ni completan colas de transferencia pendientes.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

c
__scroll_set(19,27); // Save both coordinates and write the two-scroll-byte pair.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__scroll_set: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:114

Notas de la declaración (original)
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

Sustituye X guardado, reinicia el latch leyendo el estado y escribe el nuevo X y el Y guardado en PPUSCROLL. Y procede de la copia de software, no de la PPU; las escrituras directas al registro no actualizan esa copia.

Sintaxis

c
void __scroll_x_set(u8 x);

Parámetros

x

Los desplazamientos son bytes sin signo medidos en píxeles. Use X 0..255 y normalmente Y 0..239; Y 240..255 se escribe sin cambios, pero tiene un comportamiento especial de filas PPU. PPUCTRL aporta los bits de tabla base. Restaure el desplazamiento tras el acceso directo a VRAM y antes de renderizar; la función no programa ese momento.

Valor devuelto

Ninguno (void).

Condiciones y límites

Leer PPUSTATUS puede afectar la temporización de NMI y las siguientes escrituras a PPUSCROLL y PPUADDR. No lo inserte entre los dos bytes de otro procedimiento. Estas llamadas no esperan VBlank ni completan colas de transferencia pendientes.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

c
__scroll_x_set(0); // Keeps saved Y=27 while changing X to zero.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__scroll_x_set: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:116

Notas de la declaración (original)
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

Sustituye Y guardado, reinicia el latch leyendo el estado y escribe el X guardado y el nuevo Y en PPUSCROLL. Escribe ambos bytes aunque solo cambie Y. X procede de la copia del runtime.

Sintaxis

c
void __scroll_y_set(u8 y);

Parámetros

y

Los desplazamientos son bytes sin signo medidos en píxeles. Use X 0..255 y normalmente Y 0..239; Y 240..255 se escribe sin cambios, pero tiene un comportamiento especial de filas PPU. PPUCTRL aporta los bits de tabla base. Restaure el desplazamiento tras el acceso directo a VRAM y antes de renderizar; la función no programa ese momento.

Valor devuelto

Ninguno (void).

Condiciones y límites

Leer PPUSTATUS puede afectar la temporización de NMI y las siguientes escrituras a PPUSCROLL y PPUADDR. No lo inserte entre los dos bytes de otro procedimiento. Estas llamadas no esperan VBlank ni completan colas de transferencia pendientes.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

c
__scroll_y_set(0); // Keeps saved X=0; the scene is now at the origin.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__scroll_y_set: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:118

Notas de la declaración (original)
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

Calcula un producto escalar con signo de dos términos.

Sintaxis

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

Parámetros

a0

Patrón de bits de 16 bits con signo que representa de −32768 a 32767.

a1

Patrón de bits de 16 bits con signo que representa de −32768 a 32767.

b0

Patrón de bits de un byte con signo, coeficiente de −128 a 127.

b1

Patrón de bits de un byte con signo, coeficiente de −128 a 127.

Valor devuelto

Devuelve los 16 bits bajos de a0*b0+a1*b1.

Condiciones y límites

En FC, los nombres q8_8/q1_7 usan productos enteros sin el desplazamiento de escala de punto fijo. Las funciones Q1.7 son alias de sus operaciones con signo correspondientes. El código que llama gestiona las unidades; los resultados con signo se devuelven como patrones de bits u16.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Compara entradas literales con valores guardados en RAM. Los resultados representativos se muestran como valores de 16 bits sin signo; los negativos usan patrones en complemento a dos. GB ajusta la escala de los productos de punto fijo, mientras FC devuelve productos enteros, por lo que entradas idénticas pueden dar resultados distintos.

El fragmento guarda estos valores de 16 bits: result[34]=64959, result[35]=64959, result[36]=577, result[37]=577. Cada cálculo con entrada literal coincide con su equivalente con entrada en RAM. La pantalla muestra valores representativos; el array result guarda todos los resultados.

__sdot2_q1_7: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:71

Notas de la declaración (original)
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

Calcula un producto escalar con signo de dos términos.

Sintaxis

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

Parámetros

a0

Patrón de bits de 16 bits con signo que representa de −32768 a 32767.

a1

Patrón de bits de 16 bits con signo que representa de −32768 a 32767.

b0

Patrón de bits de un byte con signo, coeficiente de −128 a 127.

b1

Patrón de bits de un byte con signo, coeficiente de −128 a 127.

Valor devuelto

Devuelve los 16 bits bajos de a0*b0+a1*b1.

Condiciones y límites

En FC, los nombres q8_8/q1_7 usan productos enteros sin el desplazamiento de escala de punto fijo. Las funciones Q1.7 son alias de sus operaciones con signo correspondientes. El código que llama gestiona las unidades; los resultados con signo se devuelven como patrones de bits u16.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Compara entradas literales con valores guardados en RAM. Los resultados representativos se muestran como valores de 16 bits sin signo; los negativos usan patrones en complemento a dos. GB ajusta la escala de los productos de punto fijo, mientras FC devuelve productos enteros, por lo que entradas idénticas pueden dar resultados distintos.

El fragmento guarda estos valores de 16 bits: result[26]=64959, result[27]=64959, result[28]=577, result[29]=577. Cada cálculo con entrada literal coincide con su equivalente con entrada en RAM. La pantalla muestra valores representativos; el array result guarda todos los resultados.

__sdot2_q8_8: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:67

Notas de la declaración (original)
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

Calcula un producto escalar con signo de tres términos.

Sintaxis

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

Parámetros

a0

Patrón de bits de 16 bits con signo que representa de −32768 a 32767.

a1

Patrón de bits de 16 bits con signo que representa de −32768 a 32767.

a2

Patrón de bits de 16 bits con signo que representa de −32768 a 32767.

b0

Patrón de bits de un byte con signo, coeficiente de −128 a 127.

b1

Patrón de bits de un byte con signo, coeficiente de −128 a 127.

b2

Patrón de bits de un byte con signo, coeficiente de −128 a 127.

Valor devuelto

Devuelve los 16 bits bajos de a0*b0+a1*b1+a2*b2.

Condiciones y límites

En FC, los nombres q8_8/q1_7 usan productos enteros sin el desplazamiento de escala de punto fijo. Las funciones Q1.7 son alias de sus operaciones con signo correspondientes. El código que llama gestiona las unidades; los resultados con signo se devuelven como patrones de bits u16.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Compara entradas literales con valores guardados en RAM. Los resultados representativos se muestran como valores de 16 bits sin signo; los negativos usan patrones en complemento a dos. GB ajusta la escala de los productos de punto fijo, mientras FC devuelve productos enteros, por lo que entradas idénticas pueden dar resultados distintos.

El fragmento guarda estos valores de 16 bits: result[38]=2498, result[39]=2498, result[40]=63038, result[41]=63038. Cada cálculo con entrada literal coincide con su equivalente con entrada en RAM. La pantalla muestra valores representativos; el array result guarda todos los resultados.

__sdot3_q1_7: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:73

Notas de la declaración (original)
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

Calcula un producto escalar con signo de tres términos.

Sintaxis

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

Parámetros

a0

Patrón de bits de 16 bits con signo que representa de −32768 a 32767.

a1

Patrón de bits de 16 bits con signo que representa de −32768 a 32767.

a2

Patrón de bits de 16 bits con signo que representa de −32768 a 32767.

b0

Patrón de bits de un byte con signo, coeficiente de −128 a 127.

b1

Patrón de bits de un byte con signo, coeficiente de −128 a 127.

b2

Patrón de bits de un byte con signo, coeficiente de −128 a 127.

Valor devuelto

Devuelve los 16 bits bajos de a0*b0+a1*b1+a2*b2.

Condiciones y límites

En FC, los nombres q8_8/q1_7 usan productos enteros sin el desplazamiento de escala de punto fijo. Las funciones Q1.7 son alias de sus operaciones con signo correspondientes. El código que llama gestiona las unidades; los resultados con signo se devuelven como patrones de bits u16.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Compara entradas literales con valores guardados en RAM. Los resultados representativos se muestran como valores de 16 bits sin signo; los negativos usan patrones en complemento a dos. GB ajusta la escala de los productos de punto fijo, mientras FC devuelve productos enteros, por lo que entradas idénticas pueden dar resultados distintos.

El fragmento guarda estos valores de 16 bits: result[30]=2498, result[31]=2498, result[32]=63038, result[33]=63038. Cada cálculo con entrada literal coincide con su equivalente con entrada en RAM. La pantalla muestra valores representativos; el array result guarda todos los resultados.

__sdot3_q8_8: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:69

Notas de la declaración (original)
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

Muestrea una vez el bit 4 de $4017 como línea de recepción de un adaptador externo.

Sintaxis

c
u8 __serial_rx_bit(void);

Valor devuelto

1 si el bit 4 está activado; en caso contrario, 0. No es un indicador de datos disponibles ni de error de comunicación.

Condiciones y límites

Estas API acceden directamente a $4016/$4017. No intercales operaciones de mando, teclado o serie mediante interrupciones en los mismos puertos. También pueden afectar a las posiciones de desplazamiento del controlador. El ejemplo deshabilita NMI mientras captura entradas y la habilita al inicializar la pantalla.

Estas primitivas suponen que un adaptador externo conecta OUT0 a TX y $4017 D4 a RX. No gestionan el intervalo entre bits, el reloj de transmisión, los bits de inicio y parada, la formación de bytes ni la espera de recepción. Su función es distinta de la de las rutinas por bytes, como __midi_out().

El código que llama debe muestrear en el momento adecuado. La función no espera datos ni agrupa varios bits, y tampoco modifica la salida de $4016.

La conversión de bits también se comprueba en una ROM de prueba propia, redirigiendo las lecturas de registros a valores de prueba en RAM. Se cubren las combinaciones 00/08/10/18 con otros bits activados. Esta prueba unitaria es independiente de la ejecución del ejemplo sin modificar y del comportamiento de un periférico real.

Estas imágenes ejecutan las ROM descargables sin cambios en KUROSAKI. Su bus de entrada modela mandos, pero no señales de teclado, pistola de luz ni adaptador serie. Los bits externos se leen como cero, por lo que las capturas no verifican esos dispositivos. No se han probado teclas físicas, detección óptica ni comunicación.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Para VALUE 000/001/002/003/254/255, EXPECTED OUT0 vale 0/1/0/1/0/1. Esta columna explica la correspondencia del bit menos significativo; no es una lectura del nivel eléctrico de salida. Las trazas de CPU confirman esas escrituras, seguidas de una última escritura de 1. RX SAMPLE vale 000 porque KUROSAKI no emula la línea de recepción.

__serial_rx_bit: SERIAL-DEFAULT — Para VALUE 000/001/002/003/254/255, EXPECTED OUT0 vale 0/1/0/1/0/1. Esta columna explica la correspondencia del bit menos significativo; no es una lectura del nivel eléctrico de salida. Las trazas de CPU confirman esas escrituras, seguidas de una última escritura de 1. RX SAMPLE vale 000 porque KUROSAKI no emula la línea de recepción.
SERIAL-DEFAULT — Para VALUE 000/001/002/003/254/255, EXPECTED OUT0 vale 0/1/0/1/0/1. Esta columna explica la correspondencia del bit menos significativo; no es una lectura del nivel eléctrico de salida. Las trazas de CPU confirman esas escrituras, seguidas de una última escritura de 1. RX SAMPLE vale 000 porque KUROSAKI no emula la línea de recepción.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:288

Notas de la declaración (original)
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

Escribe el bit menos significativo del argumento en OUT0 a través de $4016, como operación de salida de un bit para un adaptador serie externo.

Sintaxis

c
void __serial_tx_bit(u8 bit);

Parámetros

bit

Valor de salida. Solo se usa bit & 1: 2 emite cero y 255 emite uno.

Valor devuelto

No devuelve ningún valor.

Condiciones y límites

Estas API acceden directamente a $4016/$4017. No intercales operaciones de mando, teclado o serie mediante interrupciones en los mismos puertos. También pueden afectar a las posiciones de desplazamiento del controlador. El ejemplo deshabilita NMI mientras captura entradas y la habilita al inicializar la pantalla.

Estas primitivas suponen que un adaptador externo conecta OUT0 a TX y $4017 D4 a RX. No gestionan el intervalo entre bits, el reloj de transmisión, los bits de inicio y parada, la formación de bytes ni la espera de recepción. Su función es distinta de la de las rutinas por bytes, como __midi_out().

Escribe cero o uno en todo $4016, poniendo OUT1/OUT2 a cero. No inserta un tiempo de mantenimiento del nivel. OUT0 controla la captura de botones del mando normal, así que esta operación también modifica el estado de lectura del mando.

Estas imágenes ejecutan las ROM descargables sin cambios en KUROSAKI. Su bus de entrada modela mandos, pero no señales de teclado, pistola de luz ni adaptador serie. Los bits externos se leen como cero, por lo que las capturas no verifican esos dispositivos. No se han probado teclas físicas, detección óptica ni comunicación.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Para VALUE 000/001/002/003/254/255, EXPECTED OUT0 vale 0/1/0/1/0/1. Esta columna explica la correspondencia del bit menos significativo; no es una lectura del nivel eléctrico de salida. Las trazas de CPU confirman esas escrituras, seguidas de una última escritura de 1. RX SAMPLE vale 000 porque KUROSAKI no emula la línea de recepción.

__serial_tx_bit: SERIAL-DEFAULT — Para VALUE 000/001/002/003/254/255, EXPECTED OUT0 vale 0/1/0/1/0/1. Esta columna explica la correspondencia del bit menos significativo; no es una lectura del nivel eléctrico de salida. Las trazas de CPU confirman esas escrituras, seguidas de una última escritura de 1. RX SAMPLE vale 000 porque KUROSAKI no emula la línea de recepción.
SERIAL-DEFAULT — Para VALUE 000/001/002/003/254/255, EXPECTED OUT0 vale 0/1/0/1/0/1. Esta columna explica la correspondencia del bit menos significativo; no es una lectura del nivel eléctrico de salida. Las trazas de CPU confirman esas escrituras, seguidas de una última escritura de 1. RX SAMPLE vale 000 porque KUROSAKI no emula la línea de recepción.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:286

Notas de la declaración (original)
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

Calcula una multiplicación-acumulación con signo.

Sintaxis

c
u16 __smac16(u16 acc, u16 a, u8 b);

Parámetros

acc

Acumulador de 16 bits que se va a sumar.

a

Patrón de bits de 16 bits con signo que representa de −32768 a 32767.

b

Patrón de bits de un byte con signo, coeficiente de −128 a 127.

Valor devuelto

Devuelve los 16 bits bajos de acc+a*b.

Condiciones y límites

En FC, los nombres q8_8/q1_7 usan productos enteros sin el desplazamiento de escala de punto fijo. Las funciones Q1.7 son alias de sus operaciones con signo correspondientes. El código que llama gestiona las unidades; los resultados con signo se devuelven como patrones de bits u16.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Compara entradas literales con valores guardados en RAM. Los resultados representativos se muestran como valores de 16 bits sin signo; los negativos usan patrones en complemento a dos. GB ajusta la escala de los productos de punto fijo, mientras FC devuelve productos enteros, por lo que entradas idénticas pueden dar resultados distintos.

El fragmento guarda estos valores de 16 bits: result[10]=32796, result[11]=32796, result[12]=32668, result[13]=32668. Cada cálculo con entrada literal coincide con su equivalente con entrada en RAM. La pantalla muestra valores representativos; el array result guarda todos los resultados.

__smac16: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:57

Notas de la declaración (original)
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

Calcula una multiplicación-acumulación con signo.

Sintaxis

c
u16 __smac16_q1_7(u16 acc, u16 a, u8 b);

Parámetros

acc

Acumulador de 16 bits que se va a sumar.

a

Patrón de bits de 16 bits con signo que representa de −32768 a 32767.

b

Patrón de bits de un byte con signo, coeficiente de −128 a 127.

Valor devuelto

Devuelve los 16 bits bajos de acc+a*b.

Condiciones y límites

En FC, los nombres q8_8/q1_7 usan productos enteros sin el desplazamiento de escala de punto fijo. Las funciones Q1.7 son alias de sus operaciones con signo correspondientes. El código que llama gestiona las unidades; los resultados con signo se devuelven como patrones de bits u16.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Compara entradas literales con valores guardados en RAM. Los resultados representativos se muestran como valores de 16 bits sin signo; los negativos usan patrones en complemento a dos. GB ajusta la escala de los productos de punto fijo, mientras FC devuelve productos enteros, por lo que entradas idénticas pueden dar resultados distintos.

El fragmento guarda estos valores de 16 bits: result[18]=32796, result[19]=32796, result[20]=32668, result[21]=32668. Cada cálculo con entrada literal coincide con su equivalente con entrada en RAM. La pantalla muestra valores representativos; el array result guarda todos los resultados.

__smac16_q1_7: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:61

Notas de la declaración (original)
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

Calcula una multiplicación con signo.

Sintaxis

c
u16 __smul16x8(u16 a, u8 b);

Parámetros

a

Patrón de bits de 16 bits con signo que representa de −32768 a 32767.

b

Patrón de bits de un byte con signo, coeficiente de −128 a 127.

Valor devuelto

Devuelve los 16 bits bajos de a*b.

Condiciones y límites

En FC, los nombres q8_8/q1_7 usan productos enteros sin el desplazamiento de escala de punto fijo. Las funciones Q1.7 son alias de sus operaciones con signo correspondientes. El código que llama gestiona las unidades; los resultados con signo se devuelven como patrones de bits u16.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Compara entradas literales con valores guardados en RAM. Los resultados representativos se muestran como valores de 16 bits sin signo; los negativos usan patrones en complemento a dos. GB ajusta la escala de los productos de punto fijo, mientras FC devuelve productos enteros, por lo que entradas idénticas pueden dar resultados distintos.

El fragmento guarda estos valores de 16 bits: result[4]=32832, result[5]=32832, result[6]=32704, result[7]=32704. Cada cálculo con entrada literal coincide con su equivalente con entrada en RAM. La pantalla muestra valores representativos; el array result guarda todos los resultados.

__smul16x8: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:53

Notas de la declaración (original)
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

Calcula una multiplicación con signo.

Sintaxis

c
u16 __smul16x8_q1_7(u16 a, u8 b);

Parámetros

a

Patrón de bits de 16 bits con signo que representa de −32768 a 32767.

b

Patrón de bits de un byte con signo, coeficiente de −128 a 127.

Valor devuelto

Devuelve los 16 bits bajos de a*b.

Condiciones y límites

En FC, los nombres q8_8/q1_7 usan productos enteros sin el desplazamiento de escala de punto fijo. Las funciones Q1.7 son alias de sus operaciones con signo correspondientes. El código que llama gestiona las unidades; los resultados con signo se devuelven como patrones de bits u16.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Compara entradas literales con valores guardados en RAM. Los resultados representativos se muestran como valores de 16 bits sin signo; los negativos usan patrones en complemento a dos. GB ajusta la escala de los productos de punto fijo, mientras FC devuelve productos enteros, por lo que entradas idénticas pueden dar resultados distintos.

El fragmento guarda estos valores de 16 bits: result[14]=32832, result[15]=32832, result[16]=32704, result[17]=32704. Cada cálculo con entrada literal coincide con su equivalente con entrada en RAM. La pantalla muestra valores representativos; el array result guarda todos los resultados.

__smul16x8_q1_7: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:59

Notas de la declaración (original)
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

Espera un nuevo hit de sprite cero y escribe X/Y para una división horizontal, conservando la alineación superior y desplazando el fondo inferior.

Sintaxis

c
void __split_scroll_sprite0(u8 x, u8 y);

Parámetros

x

Desplazamiento horizontal de 0 a 255, no coordenada X de un objeto. El ejemplo usa 32 para mostrar la zona inferior del mismo fondo desplazada 32 píxeles a la izquierda.

y

Ajuste temporal de desplazamiento vertical de 0 a 255; el rango visible habitual es 0..239. Escribe $2005, por lo que no cambia inmediatamente la posición vertical activa durante el renderizado. El ejemplo de división horizontal pasa cero.

Valor devuelto

No devuelve ningún valor.

Condiciones y límites

Superpón un píxel opaco del sprite OAM 0 con uno opaco del fondo, con ambas capas activas. La prioridad detrás del fondo sigue permitiendo un hit. Los ocho píxeles de la izquierda requieren habilitar cada capa en ese borde; en X=255 el solapamiento no dispara un hit.

Consulta el bit 6 de PPUSTATUS ($2002) hasta que se borre y después hasta que se active. Si ya hubo un hit al llamar, espera uno nuevo en el siguiente fotograma. Leer la bandera no la confirma ni la borra; el PPU la borra durante el prerenderizado.

No hay tiempo máximo ni retorno de fallo. Un sprite 0 oculto, fondo transparente, renderizado desactivado u otra configuración sin solapamiento deja la llamada bloqueada. Garantiza un hit antes de llamar. No requiere habilitar IRQ de CPU ni activa NMI. El ejemplo habilita NMI para su espera de fotograma independiente.

Leer $2002 también afecta al latch compartido de escritura de dirección/desplazamiento y a la bandera VBlank. No intercales esta llamada entre las dos escrituras de $2005/$2006. Úsala desde el código principal, no desde NMI/IRQ.

Tras el hit, lee $2002 y escribe X y después Y en $2005. La X fina cambia de inmediato; la gruesa, en la siguiente recarga horizontal. Y suele aplicarse en la siguiente recarga de prerenderizado, salvo que otra escritura la sustituya antes. No es un cambio ráster inmediato a cualquier X/Y.

La consulta y las instrucciones retrasan las escrituras. KUROSAKI renderiza por líneas, por lo que las capturas no demuestran un límite exacto por punto en hardware. El ejemplo sitúa el cambio dentro de la franja blanca Y=96..103, alejando la incertidumbre de las barras azules y rojas.

La cabecera compartida fc_sprite0_scene.h prepara barras originales azules/rojas y una franja blanca horizontal. El sprite 0 está en (16,96), oculto tras el fondo; su Y OAM bruto es 95 porque Y visible=Y OAM+1. Cada fotograma restaura el desplazamiento superior a cero en VBlank antes de esperar el hit.

NESdev: desplazamiento de PPU

NESdev: referencia de PPU para programadores

Ejemplo de uso

c
void main(void) {
    s0_prepare();
    while (1) {
        s0_frame();
        __split_scroll_sprite0(32,0);
        s0_completed++;
    }
}

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Encima de la franja blanca, azul en X=64..95 y rojo en X=160..191. Debajo, desde Y=104, azul pasa a X=32..63 y rojo a X=128..159: solo la zona inferior se mueve 32 píxeles a la izquierda. La franja está en Y=96..103 y las barras terminan en Y=207. La verificación comprueba una finalización por fotograma y la posición y color de cada píxel.

__split_scroll_sprite0: SPLIT-DEFAULT — Encima de la franja blanca, azul en X=64..95 y rojo en X=160..191. Debajo, desde Y=104, azul pasa a X=32..63 y rojo a X=128..159: solo la zona inferior se mueve 32 píxeles a la izquierda. La franja está en Y=96..103 y las barras terminan en Y=207. La verificación comprueba una finalización por fotograma y la posición y color de cada píxel.
SPLIT-DEFAULT — Encima de la franja blanca, azul en X=64..95 y rojo en X=160..191. Debajo, desde Y=104, azul pasa a X=32..63 y rojo a X=128..159: solo la zona inferior se mueve 32 píxeles a la izquierda. La franja está en Y=96..103 y las barras terminan en Y=207. La verificación comprueba una finalización por fotograma y la posición y color de cada píxel.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:336

Notas de la declaración (original)
Wait for a new sprite-zero hit, reset the PPU latch and write X/Y scroll; requires a visible hit setup.
__sprite0_wait_hit

Espera un nuevo solapamiento sprite cero/fondo para sincronizar el trabajo principal con una zona conocida de pantalla sin cambiar el desplazamiento.

Sintaxis

c
void __sprite0_wait_hit(void);

Valor devuelto

No devuelve ningún valor.

Condiciones y límites

Superpón un píxel opaco del sprite OAM 0 con uno opaco del fondo, con ambas capas activas. La prioridad detrás del fondo sigue permitiendo un hit. Los ocho píxeles de la izquierda requieren habilitar cada capa en ese borde; en X=255 el solapamiento no dispara un hit.

Consulta el bit 6 de PPUSTATUS ($2002) hasta que se borre y después hasta que se active. Si ya hubo un hit al llamar, espera uno nuevo en el siguiente fotograma. Leer la bandera no la confirma ni la borra; el PPU la borra durante el prerenderizado.

No hay tiempo máximo ni retorno de fallo. Un sprite 0 oculto, fondo transparente, renderizado desactivado u otra configuración sin solapamiento deja la llamada bloqueada. Garantiza un hit antes de llamar. No requiere habilitar IRQ de CPU ni activa NMI. El ejemplo habilita NMI para su espera de fotograma independiente.

Leer $2002 también afecta al latch compartido de escritura de dirección/desplazamiento y a la bandera VBlank. No intercales esta llamada entre las dos escrituras de $2005/$2006. Úsala desde el código principal, no desde NMI/IRQ.

La consulta y las instrucciones retrasan las escrituras. KUROSAKI renderiza por líneas, por lo que las capturas no demuestran un límite exacto por punto en hardware. El ejemplo sitúa el cambio dentro de la franja blanca Y=96..103, alejando la incertidumbre de las barras azules y rojas.

La cabecera compartida fc_sprite0_scene.h prepara barras originales azules/rojas y una franja blanca horizontal. El sprite 0 está en (16,96), oculto tras el fondo; su Y OAM bruto es 95 porque Y visible=Y OAM+1. Cada fotograma restaura el desplazamiento superior a cero en VBlank antes de esperar el hit.

NESdev: desplazamiento de PPU

NESdev: referencia de PPU para programadores

Ejemplo de uso

c
void main(void) {
    s0_prepare();
    while (1) {
        s0_frame();
        __sprite0_wait_hit();
        s0_completed++;
    }
}

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

La barra azul ocupa X=64..95 y la roja X=160..191, alineadas por encima y debajo de la franja blanca. Esperar no cambia el desplazamiento. El ejemplo ejecutado completa 116 esperas tras 120 fotogramas y 117 tras 121, verificando que sigue avanzando.

__sprite0_wait_hit: WAIT-DEFAULT — La barra azul ocupa X=64..95 y la roja X=160..191, alineadas por encima y debajo de la franja blanca. Esperar no cambia el desplazamiento. El ejemplo ejecutado completa 116 esperas tras 120 fotogramas y 117 tras 121, verificando que sigue avanzando.
WAIT-DEFAULT — La barra azul ocupa X=64..95 y la roja X=160..191, alineadas por encima y debajo de la franja blanca. Esperar no cambia el desplazamiento. El ejemplo ejecutado completa 116 esperas tras 120 fotogramas y 117 tras 121, verificando que sigue avanzando.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:334

Notas de la declaración (original)
Wait for the current hit flag to clear, then for a new hit. No timeout is provided.
__sprite_attr

Sustituye el byte completo de atributos de un sprite del búfer auxiliar. Permite elegir la paleta, la prioridad frente al fondo y las inversiones horizontal o vertical sin cambiar el mosaico ni la posición.

Sintaxis

c
void __sprite_attr(u8 index, u8 attr);

Parámetros

index

Índice de sprite entre 0 y 63, no desplazamiento en bytes. La implementación lo multiplica por cuatro con un cursor de 8 bits; los valores fuera de rango se reducen módulo 64 y pueden sobrescribir otra entrada. No se comprueba si está reservada.

attr

Byte completo de atributos: bits 0–1 seleccionan la paleta 0–3; bit 5 coloca los píxeles opacos del sprite detrás de los del fondo; bits 6 y 7 invierten horizontal y verticalmente. Mantenga los bits 2–4 a cero. Combine banderas con OR a nivel de bits; el byte sustituye todos los atributos anteriores.

Valor devuelto

Ninguno (void).

Condiciones y límites

Estas operaciones modifican la RAM de CPU 0x0200–0x02FF; no actualizan inmediatamente la OAM del hardware. Transfiera la página completa con __oam_dma. No mantienen las marcas de reserva, las matrices de coordenadas ni el contador de la biblioteca de sprites. Evite que una interrupción modifique o transfiera una página a medio preparar.

Ejemplo de uso

c
__sprite_attr(3,0xC0); // Flip the arrow horizontally and vertically.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo dedica una fila rotulada a cada operación. SET y MOVE muestran flechas de 8×8 hacia la derecha en (144,16) y (144,32); TILE, un cuadrado verde en (144,48); ATTR, la flecha invertida en ambos ejes en (144,64). HIDE en (144,80) y CLEAR en (144,112) deben quedar vacíos. META muestra dos flechas en (136,96) y (144,96), la segunda reflejada horizontalmente. NEXT 07 indica el siguiente índice tras escribir las entradas 5 y 6. Los bordes son rojos, el cuerpo verde y los detalles de la punta azules. Se comparan 8.704 píxeles por imagen y los bytes OAM transferidos; no se ha probado en hardware físico.

TRANSFER muestra una flecha en (144,128). Este programa envía la página 02 directamente con __oam_dma, incluidos todos los campos preparados por las operaciones individuales de sprites.

__sprite_attr: PAGE02
PAGE02

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:198

Notas de la declaración (original)
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

Oculta un sprite del búfer auxiliar poniendo su byte Y a 0xF0. Conserva X, el mosaico y los atributos para reutilizarlos. No libera una reserva ni modifica el contador de sprites activos de la biblioteca.

Sintaxis

c
void __sprite_hide(u8 index);

Parámetros

index

Índice de sprite entre 0 y 63, no desplazamiento en bytes. La implementación lo multiplica por cuatro con un cursor de 8 bits; los valores fuera de rango se reducen módulo 64 y pueden sobrescribir otra entrada. No se comprueba si está reservada.

Valor devuelto

Ninguno (void).

Condiciones y límites

Estas operaciones modifican la RAM de CPU 0x0200–0x02FF; no actualizan inmediatamente la OAM del hardware. Transfiera la página completa con __oam_dma. No mantienen las marcas de reserva, las matrices de coordenadas ni el contador de la biblioteca de sprites. Evite que una interrupción modifique o transfiera una página a medio preparar.

Ejemplo de uso

c
__sprite_hide(4); // The HIDE row remains empty after DMA.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo dedica una fila rotulada a cada operación. SET y MOVE muestran flechas de 8×8 hacia la derecha en (144,16) y (144,32); TILE, un cuadrado verde en (144,48); ATTR, la flecha invertida en ambos ejes en (144,64). HIDE en (144,80) y CLEAR en (144,112) deben quedar vacíos. META muestra dos flechas en (136,96) y (144,96), la segunda reflejada horizontalmente. NEXT 07 indica el siguiente índice tras escribir las entradas 5 y 6. Los bordes son rojos, el cuerpo verde y los detalles de la punta azules. Se comparan 8.704 píxeles por imagen y los bytes OAM transferidos; no se ha probado en hardware físico.

TRANSFER muestra una flecha en (144,128). Este programa envía la página 02 directamente con __oam_dma, incluidos todos los campos preparados por las operaciones individuales de sprites.

__sprite_hide: PAGE02
PAGE02

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:200

Notas de la declaración (original)
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

Cambia únicamente los bytes X e Y de un sprite del búfer auxiliar. Conserva el mosaico y los atributos, de modo que la imagen se mueve sin cambiar de paleta ni de orientación.

Sintaxis

c
void __sprite_move(u8 index, u8 x, u8 y);

Parámetros

index

Índice de sprite entre 0 y 63, no desplazamiento en bytes. La implementación lo multiplica por cuatro con un cursor de 8 bits; los valores fuera de rango se reducen módulo 64 y pueden sobrescribir otra entrada. No se comprueba si está reservada.

x

Posición horizontal en píxeles, expresada como byte sin signo; no es una coordenada de patrón.

y

Byte Y de OAM. Para situar el borde superior por debajo de la línea cero, pasa su posición menos uno; 239..255 quedan fuera de las 240 líneas visibles.

Valor devuelto

Ninguno (void).

Condiciones y límites

Estas operaciones modifican la RAM de CPU 0x0200–0x02FF; no actualizan inmediatamente la OAM del hardware. Transfiera la página completa con __oam_dma. No mantienen las marcas de reserva, las matrices de coordenadas ni el contador de la biblioteca de sprites. Evite que una interrupción modifique o transfiera una página a medio preparar.

Ejemplo de uso

c
__sprite_move(1,144,31); // Move to (144,32), retaining tile 128 and attributes 0.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo dedica una fila rotulada a cada operación. SET y MOVE muestran flechas de 8×8 hacia la derecha en (144,16) y (144,32); TILE, un cuadrado verde en (144,48); ATTR, la flecha invertida en ambos ejes en (144,64). HIDE en (144,80) y CLEAR en (144,112) deben quedar vacíos. META muestra dos flechas en (136,96) y (144,96), la segunda reflejada horizontalmente. NEXT 07 indica el siguiente índice tras escribir las entradas 5 y 6. Los bordes son rojos, el cuerpo verde y los detalles de la punta azules. Se comparan 8.704 píxeles por imagen y los bytes OAM transferidos; no se ha probado en hardware físico.

TRANSFER muestra una flecha en (144,128). Este programa envía la página 02 directamente con __oam_dma, incluidos todos los campos preparados por las operaciones individuales de sprites.

__sprite_move: PAGE02
PAGE02

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:194

Notas de la declaración (original)
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

Escribe los cuatro campos de un sprite en el búfer auxiliar de la página 02. Los argumentos son índice, X, Y, mosaico y atributos; el orden almacenado es Y, mosaico, atributos, X.

Sintaxis

c
void __sprite_set(u8 index, u8 x, u8 y, u8 tile, u8 attr);

Parámetros

index

Índice de sprite entre 0 y 63, no desplazamiento en bytes. La implementación lo multiplica por cuatro con un cursor de 8 bits; los valores fuera de rango se reducen módulo 64 y pueden sobrescribir otra entrada. No se comprueba si está reservada.

x

Posición horizontal en píxeles, expresada como byte sin signo; no es una coordenada de patrón.

y

Byte Y de OAM. Para situar el borde superior por debajo de la línea cero, pasa su posición menos uno; 239..255 quedan fuera de las 240 líneas visibles.

tile

Número de patrón de 8 bits. Carga antes sus gráficos. Se aplican las reglas de sprites de 8×16; la función no adapta el número a pares de patrones.

attr

Byte completo de atributos: bits 0–1 seleccionan la paleta 0–3; bit 5 coloca los píxeles opacos del sprite detrás de los del fondo; bits 6 y 7 invierten horizontal y verticalmente. Mantenga los bits 2–4 a cero. Combine banderas con OR a nivel de bits; el byte sustituye todos los atributos anteriores.

Valor devuelto

Ninguno (void).

Condiciones y límites

Estas operaciones modifican la RAM de CPU 0x0200–0x02FF; no actualizan inmediatamente la OAM del hardware. Transfiera la página completa con __oam_dma. No mantienen las marcas de reserva, las matrices de coordenadas ni el contador de la biblioteca de sprites. Evite que una interrupción modifique o transfiera una página a medio preparar.

Ejemplo de uso

c
__sprite_set(0,144,15,128,0); // An 8x8 arrow at visible position (144,16).

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo dedica una fila rotulada a cada operación. SET y MOVE muestran flechas de 8×8 hacia la derecha en (144,16) y (144,32); TILE, un cuadrado verde en (144,48); ATTR, la flecha invertida en ambos ejes en (144,64). HIDE en (144,80) y CLEAR en (144,112) deben quedar vacíos. META muestra dos flechas en (136,96) y (144,96), la segunda reflejada horizontalmente. NEXT 07 indica el siguiente índice tras escribir las entradas 5 y 6. Los bordes son rojos, el cuerpo verde y los detalles de la punta azules. Se comparan 8.704 píxeles por imagen y los bytes OAM transferidos; no se ha probado en hardware físico.

TRANSFER muestra una flecha en (144,128). Este programa envía la página 02 directamente con __oam_dma, incluidos todos los campos preparados por las operaciones individuales de sprites.

__sprite_set: PAGE02
PAGE02

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:192

Notas de la declaración (original)
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

Sustituye solo el byte del número de mosaico de un sprite del búfer auxiliar. Conserva la posición y los atributos. Selecciona un patrón existente; no carga gráficos en la memoria CHR.

Sintaxis

c
void __sprite_tile(u8 index, u8 tile);

Parámetros

index

Índice de sprite entre 0 y 63, no desplazamiento en bytes. La implementación lo multiplica por cuatro con un cursor de 8 bits; los valores fuera de rango se reducen módulo 64 y pueden sobrescribir otra entrada. No se comprueba si está reservada.

tile

Número de patrón de 8 bits. Carga antes sus gráficos. Se aplican las reglas de sprites de 8×16; la función no adapta el número a pares de patrones.

Valor devuelto

Ninguno (void).

Condiciones y límites

Estas operaciones modifican la RAM de CPU 0x0200–0x02FF; no actualizan inmediatamente la OAM del hardware. Transfiera la página completa con __oam_dma. No mantienen las marcas de reserva, las matrices de coordenadas ni el contador de la biblioteca de sprites. Evite que una interrupción modifique o transfiera una página a medio preparar.

Ejemplo de uso

c
__sprite_tile(2,129); // Select the green square at (144,48).

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El ejemplo dedica una fila rotulada a cada operación. SET y MOVE muestran flechas de 8×8 hacia la derecha en (144,16) y (144,32); TILE, un cuadrado verde en (144,48); ATTR, la flecha invertida en ambos ejes en (144,64). HIDE en (144,80) y CLEAR en (144,112) deben quedar vacíos. META muestra dos flechas en (136,96) y (144,96), la segunda reflejada horizontalmente. NEXT 07 indica el siguiente índice tras escribir las entradas 5 y 6. Los bordes son rojos, el cuerpo verde y los detalles de la punta azules. Se comparan 8.704 píxeles por imagen y los bytes OAM transferidos; no se ha probado en hardware físico.

TRANSFER muestra una flecha en (144,128). Este programa envía la página 02 directamente con __oam_dma, incluidos todos los campos preparados por las operaciones individuales de sprites.

__sprite_tile: PAGE02
PAGE02

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:196

Notas de la declaración (original)
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

Establece ppu_addr y escribe el mismo byte value en PPUDATA exactamente len veces. El relleno es directo y termina antes del retorno. No interpreta dimensiones de tiles, atributos ni límites rectangulares.

Sintaxis

c
void __vram_fill(u16 ppu_addr, u8 value, u8 len);

Parámetros

ppu_addr

Dirección PPU en bytes (0x0000–0x3FFF), no un puntero de CPU. El destino debe admitir escritura: modificar patrones requiere CHR RAM, no CHR ROM. Se aplican el mapeo y el mirroring de la PPU. Para copias y rellenos contiguos, selecciona incremento 1 en PPUCTRL. Ejecuta en un periodo seguro y restablece después el desplazamiento; el ejecutor escribe PPUADDR y PPUDATA sin restaurarlo.

value

Un byte de 0 a 255 que se repite. Su efecto visible depende de si el destino contiene índices de tiles, planos de píxeles u otra estructura de bytes.

len

Número de bytes 0..255. Cero también fija la dirección PPU y reinicia el latch, pero no escribe datos. No hay comprobación de capacidad, recorte, cambio de banco ni transferencia diferida.

Valor devuelto

Ninguno (void).

Condiciones y límites

El acceso directo a PPU requiere desactivar el renderizado o completar toda la operación dentro de un VBlank seguro. Estas funciones no esperan, encolan, bloquean NMI ni restauran el desplazamiento. Evite que una interrupción cambie el latch o la dirección compartidos durante la llamada. El bit 2 de PPUCTRL selecciona un incremento de destino de 1 o 32 por byte. La PPU gestiona los espejos y el retorno de direcciones; no se puede escribir en CHR-ROM. Restablezca el desplazamiento deseado antes de renderizar.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

c
__vram_fill(0x2060,2,32); // A second blue row of 4-pixel-wide left halves.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__vram_fill: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:122

Notas de la declaración (original)
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

Reinicia el latch PPU, establece ppu_addr y copia sincrónicamente len bytes consecutivos del origen a PPUDATA. El recuento es de ocho bits y el compilador usa lecturas indexadas. No conserva un puntero para ejecutarlo después; cambiar el origen tras el retorno no modifica la VRAM.

Sintaxis

c
void __vram_write(u16 ppu_addr, const u8* src, u8 len);

Parámetros

ppu_addr

Dirección PPU en bytes (0x0000–0x3FFF), no un puntero de CPU. El destino debe admitir escritura: modificar patrones requiere CHR RAM, no CHR ROM. Se aplican el mapeo y el mirroring de la PPU. Para copias y rellenos contiguos, selecciona incremento 1 en PPUCTRL. Ejecuta en un periodo seguro y restablece después el desplazamiento; el ejecutor escribe PPUADDR y PPUDATA sin restaurarlo.

src

Puntero CPU a un origen con al menos el número solicitado de bytes legibles. Mantenga el origen mapeado y estable hasta que termine esta transferencia síncrona. No cambia bancos, guarda el puntero en una cola ni comprueba la longitud del origen; con un recuento cero no desreferencia el puntero.

len

Número de bytes 0..255. Cero también fija la dirección PPU y reinicia el latch, pero no escribe datos. No hay comprobación de capacidad, recorte, cambio de banco ni transferencia diferida.

Valor devuelto

Ninguno (void).

Condiciones y límites

El acceso directo a PPU requiere desactivar el renderizado o completar toda la operación dentro de un VBlank seguro. Estas funciones no esperan, encolan, bloquean NMI ni restauran el desplazamiento. Evite que una interrupción cambie el latch o la dirección compartidos durante la llamada. El bit 2 de PPUCTRL selecciona un incremento de destino de 1 o 32 por byte. La PPU gestiona los espejos y el retorno de direcciones; no se puede escribir en CHR-ROM. Restablezca el desplazamiento deseado antes de renderizar.

NESdev: registros de PPU

NESdev: desplazamiento de PPU

Ejemplo de uso

c
__vram_write(0x2040,pattern,32); // Blue row: solid tile, left half, triangle.
pattern[0]=3; // The completed transfer keeps its first solid tile.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña registros, escrituras directas de tiles y atributos, desplazamiento y carga separada de paletas de fondo y sprites. La banda azul de (0,16) mide 256×16 píxeles: la primera fila repite un tile sólido de 8×8, una barra izquierda de 4×8 y un triángulo de ocho filas; la segunda contiene solo barras. Un rectángulo verde sólido de 64×32 comienza en (16,48). La región roja de 64×32 en (144,48) contiene una cuadrícula de 8 por 4 tiles triangulares.

En y=96, los marcadores azules de x=16, 24 y 32 son un cuadrado, una barra izquierda y un triángulo; en x=80 hay una barra izquierda roja y en x=144 un triángulo verde. Son tiles de fondo. En y=144, los sprites con su propia paleta son un cuadrado rojo de 8×8 en x=16, una barra izquierda azul de 4×8 en x=80 y un triángulo verde de ocho filas en x=144. El desplazamiento final es (0,0).

La imagen comprueba forma y color en 16.384 píxeles, incluidos los sprites. Hay 31 pruebas de estado: registros, efectos de estado/latch, desplazamiento guardado, transferencias de 0/255 bytes, incrementos 1/32, bordes, dimensiones cero, selectores de tabla, atributos completos y ambas paletas. Y=255 se comprueba como escritura de registro, no como una imagen de desplazamiento correcta. La evidencia procede de KUROSAKI con NROM; no se ha probado hardware real.

__vram_write: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:120

Notas de la declaración (original)
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

Devuelve la capacidad total del búfer de comandos, independientemente de su uso actual.

Sintaxis

c
u8 __vramq_capacity(void);

Valor devuelto

Un byte sin signo en las unidades de la plataforma indicadas aquí. Leerlo no modifica ni ejecuta la cola.

Condiciones y límites

FC cuenta bytes de comandos codificados en un búfer intrínseco fijo de 128 bytes. Una escritura de tile ocupa 4 bytes, un relleno 5 y una copia por puntero 6, incluidos los metadatos. NMI puede consumir una cola preparada; examínala y constrúyela antes de confirmar, coordinando productor y consumidor.

Estas intrínsecas usan el mismo búfer que lib/vram.c. Las funciones nes_vram_queue_* de runtime.c gestionan otra cola con un formato distinto. Vaciar o consultar una no vacía ni mide la otra.

Coordina las fases de construcción y ejecución de la cola. No añadas durante su ejecución en una interrupción, alteres sus contadores directamente ni sobrescribas por accidente los datos retenidos. Estas API no ofrecen bloqueo ni doble búfer. La capacidad es la del búfer de comandos, no los bytes libres de VRAM física.

Ejemplo de uso

c
capacity=__vramq_capacity();
if (capacity!=128) failures++;

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña capacidad, punteros retenidos, reinicios independientes y ejecución directa o por NMI. Se espera CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 y FAILED CHECKS 000. Seis registros ocupan 30 bytes y realizan 512 escrituras de un byte. OVERFLOW SEEN conserva el rechazo deliberado anterior; no indica que el estado final siga desbordado.

La copia azul empieza en el píxel (0,16) y el relleno verde en (0,88). Cada área ocupa siete filas completas de 256 píxeles y 248 píxeles de la octava: 255 casillas en total. La última casilla de 8 × 8 queda negra. La copia repite patrones sólidos, de mitad izquierda y triangulares; el primero es triangular porque se modificó el origen tras encolar. El relleno sigue sólido aunque cambie la variable del llamador. Se comprueban forma y color.

En y=160 deben aparecer un cuadrado rojo sólido en x=16, una mitad izquierda roja en x=224 y un cuadrado rojo sólido en x=240. El primero sobrevive a registros de longitud cero; el central prueba el consumo por NMI; el último, un put. La casilla cancelada en (248,160) queda negra. Las transferencias largas se ejecutan con renderizado y NMI apagados; solo la escritura final pequeña usa NMI. Es evidencia de emulador NROM, no de hardware real.

__vramq_capacity: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:164

Notas de la declaración (original)
Return total encoded command-buffer capacity; this is independent of PPU VRAM capacity.
__vramq_clear

Descarta todos los registros pendientes y pone a cero la longitud y la marca de disponibilidad. No escribe VRAM ni borra el indicador de desbordamiento. Para borrarlo, llama aparte a __vramq_clear_overflow.

Sintaxis

c
void __vramq_clear(void);

Valor devuelto

Ninguno (void).

Condiciones y límites

FC cuenta bytes de comandos codificados en un búfer intrínseco fijo de 128 bytes. Una escritura de tile ocupa 4 bytes, un relleno 5 y una copia por puntero 6, incluidos los metadatos. NMI puede consumir una cola preparada; examínala y constrúyela antes de confirmar, coordinando productor y consumidor.

Estas intrínsecas usan el mismo búfer que lib/vram.c. Las funciones nes_vram_queue_* de runtime.c gestionan otra cola con un formato distinto. Vaciar o consultar una no vacía ni mide la otra.

Coordina las fases de construcción y ejecución de la cola. No añadas durante su ejecución en una interrupción, alteres sus contadores directamente ni sobrescribas por accidente los datos retenidos. Estas API no ofrecen bloqueo ni doble búfer. La capacidad es la del búfer de comandos, no los bytes libres de VRAM física.

Ejemplo de uso

c
__vramq_clear();
if (__vramq_len()!=0 || __vramq_overflow()!=1) failures++;
// Clearing queued work deliberately leaves the overflow latch set.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña capacidad, punteros retenidos, reinicios independientes y ejecución directa o por NMI. Se espera CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 y FAILED CHECKS 000. Seis registros ocupan 30 bytes y realizan 512 escrituras de un byte. OVERFLOW SEEN conserva el rechazo deliberado anterior; no indica que el estado final siga desbordado.

La copia azul empieza en el píxel (0,16) y el relleno verde en (0,88). Cada área ocupa siete filas completas de 256 píxeles y 248 píxeles de la octava: 255 casillas en total. La última casilla de 8 × 8 queda negra. La copia repite patrones sólidos, de mitad izquierda y triangulares; el primero es triangular porque se modificó el origen tras encolar. El relleno sigue sólido aunque cambie la variable del llamador. Se comprueban forma y color.

En y=160 deben aparecer un cuadrado rojo sólido en x=16, una mitad izquierda roja en x=224 y un cuadrado rojo sólido en x=240. El primero sobrevive a registros de longitud cero; el central prueba el consumo por NMI; el último, un put. La casilla cancelada en (248,160) queda negra. Las transferencias largas se ejecutan con renderizado y NMI apagados; solo la escritura final pequeña usa NMI. Es evidencia de emulador NROM, no de hardware real.

__vramq_clear: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:146

Notas de la declaración (original)
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

Reinicia únicamente el indicador de desbordamiento. Conserva los registros, la longitud y la disponibilidad. Reconoce el fallo de capacidad, sin reintentar el registro rechazado ni deshacer inserciones anteriores correctas.

Sintaxis

c
void __vramq_clear_overflow(void);

Valor devuelto

Ninguno (void).

Condiciones y límites

FC cuenta bytes de comandos codificados en un búfer intrínseco fijo de 128 bytes. Una escritura de tile ocupa 4 bytes, un relleno 5 y una copia por puntero 6, incluidos los metadatos. NMI puede consumir una cola preparada; examínala y constrúyela antes de confirmar, coordinando productor y consumidor.

Estas intrínsecas usan el mismo búfer que lib/vram.c. Las funciones nes_vram_queue_* de runtime.c gestionan otra cola con un formato distinto. Vaciar o consultar una no vacía ni mide la otra.

Coordina las fases de construcción y ejecución de la cola. No añadas durante su ejecución en una interrupción, alteres sus contadores directamente ni sobrescribas por accidente los datos retenidos. Estas API no ofrecen bloqueo ni doble búfer. La capacidad es la del búfer de comandos, no los bytes libres de VRAM física.

Ejemplo de uso

c
__vramq_clear_overflow();
if (__vramq_overflow()!=0 || __vramq_len()!=4) failures++;
// Resetting the latch deliberately keeps this pending put record.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña capacidad, punteros retenidos, reinicios independientes y ejecución directa o por NMI. Se espera CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 y FAILED CHECKS 000. Seis registros ocupan 30 bytes y realizan 512 escrituras de un byte. OVERFLOW SEEN conserva el rechazo deliberado anterior; no indica que el estado final siga desbordado.

La copia azul empieza en el píxel (0,16) y el relleno verde en (0,88). Cada área ocupa siete filas completas de 256 píxeles y 248 píxeles de la octava: 255 casillas en total. La última casilla de 8 × 8 queda negra. La copia repite patrones sólidos, de mitad izquierda y triangulares; el primero es triangular porque se modificó el origen tras encolar. El relleno sigue sólido aunque cambie la variable del llamador. Se comprueban forma y color.

En y=160 deben aparecer un cuadrado rojo sólido en x=16, una mitad izquierda roja en x=224 y un cuadrado rojo sólido en x=240. El primero sobrevive a registros de longitud cero; el central prueba el consumo por NMI; el último, un put. La casilla cancelada en (248,160) queda negra. Las transferencias largas se ejecutan con renderizado y NMI apagados; solo la escritura final pequeña usa NMI. Es evidencia de emulador NROM, no de hardware real.

__vramq_clear_overflow: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:162

Notas de la declaración (original)
Clear the overflow latch explicitly after handling the failed submission.
__vramq_commit

Activa la marca de disponibilidad para que el ejecutor pueda consumir la cola. No copia datos, escribe en la PPU ni espera a NMI. Tras confirmar, mantén la cola y las fuentes retenidas estables hasta terminar. Con NMI habilitada, el manejador predeterminado puede consumirla de forma asíncrona.

Sintaxis

c
void __vramq_commit(void);

Valor devuelto

Ninguno (void).

Condiciones y límites

FC cuenta bytes de comandos codificados en un búfer intrínseco fijo de 128 bytes. Una escritura de tile ocupa 4 bytes, un relleno 5 y una copia por puntero 6, incluidos los metadatos. NMI puede consumir una cola preparada; examínala y constrúyela antes de confirmar, coordinando productor y consumidor.

Estas intrínsecas usan el mismo búfer que lib/vram.c. Las funciones nes_vram_queue_* de runtime.c gestionan otra cola con un formato distinto. Vaciar o consultar una no vacía ni mide la otra.

Coordina las fases de construcción y ejecución de la cola. No añadas durante su ejecución en una interrupción, alteres sus contadores directamente ni sobrescribas por accidente los datos retenidos. Estas API no ofrecen bloqueo ni doble búfer. La capacidad es la del búfer de comandos, no los bytes libres de VRAM física.

Ejemplo de uso

c
__vramq_commit(); // NMI is disabled here: this only marks the queue ready.
if (__vramq_len()!=30) failures++;

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña capacidad, punteros retenidos, reinicios independientes y ejecución directa o por NMI. Se espera CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 y FAILED CHECKS 000. Seis registros ocupan 30 bytes y realizan 512 escrituras de un byte. OVERFLOW SEEN conserva el rechazo deliberado anterior; no indica que el estado final siga desbordado.

La copia azul empieza en el píxel (0,16) y el relleno verde en (0,88). Cada área ocupa siete filas completas de 256 píxeles y 248 píxeles de la octava: 255 casillas en total. La última casilla de 8 × 8 queda negra. La copia repite patrones sólidos, de mitad izquierda y triangulares; el primero es triangular porque se modificó el origen tras encolar. El relleno sigue sólido aunque cambie la variable del llamador. Se comprueban forma y color.

En y=160 deben aparecer un cuadrado rojo sólido en x=16, una mitad izquierda roja en x=224 y un cuadrado rojo sólido en x=240. El primero sobrevive a registros de longitud cero; el central prueba el consumo por NMI; el último, un put. La casilla cancelada en (248,160) queda negra. Las transferencias largas se ejecutan con renderizado y NMI apagados; solo la escritura final pequeña usa NMI. Es evidencia de emulador NROM, no de hardware real.

__vramq_commit: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:154

Notas de la declaración (original)
Publish the pending records to the configured consumer. Do not mutate them while consumption can run.
__vramq_copy

Añade un registro de seis bytes para copiar len bytes de src a ppu_addr durante la ejecución. Conserva el puntero, no los datos. Incluso una copia de 255 bytes ocupa solo seis bytes de cola. Lee el origen en orden creciente; selecciona incremento PPU 1 para un destino contiguo.

Sintaxis

c
void __vramq_copy(u16 ppu_addr, const u8* src, u8 len);

Parámetros

ppu_addr

Dirección PPU en bytes (0x0000–0x3FFF), no un puntero de CPU. El destino debe admitir escritura: modificar patrones requiere CHR RAM, no CHR ROM. Se aplican el mapeo y el mirroring de la PPU. Para copias y rellenos contiguos, selecciona incremento 1 en PPUCTRL. Ejecuta en un periodo seguro y restablece después el desplazamiento; el ejecutor escribe PPUADDR y PPUDATA sin restaurarlo.

src

Puntero cercano a bytes legibles, conservado hasta la ejecución. Mantén los datos vivos y su banco de memoria o ROM visible durante toda la ejecución; no se guarda un número de banco ni una copia. Una transferencia no vacía necesita un origen válido. No uses un array local que haya caducado.

len

Número de bytes, de 0 a 255. Un registro maneja todo el recuento; no se divide en tramos de 127 bytes. Cero sigue ocupando un registro de copia o relleno. Al ejecutarlo se establece la dirección PPU, pero no se escribe en PPUDATA. 256 queda fuera del intervalo admitido.

Valor devuelto

Ninguno (void).

Condiciones y límites

La inserción no devuelve un valor de éxito. Si no cabe todo el registro, activa __vramq_overflow() y conserva registros y longitud. Se acepta un ajuste exacto. Un indicador previo no impide añadir si hay espacio; atiéndelo o reinícialo antes de juzgar un lote nuevo. Varias inserciones no forman una transacción.

FC cuenta bytes de comandos codificados en un búfer intrínseco fijo de 128 bytes. Una escritura de tile ocupa 4 bytes, un relleno 5 y una copia por puntero 6, incluidos los metadatos. NMI puede consumir una cola preparada; examínala y constrúyela antes de confirmar, coordinando productor y consumidor.

Estas intrínsecas usan el mismo búfer que lib/vram.c. Las funciones nes_vram_queue_* de runtime.c gestionan otra cola con un formato distinto. Vaciar o consultar una no vacía ni mide la otra.

Coordina las fases de construcción y ejecución de la cola. No añadas durante su ejecución en una interrupción, alteres sus contadores directamente ni sobrescribas por accidente los datos retenidos. Estas API no ofrecen bloqueo ni doble búfer. La capacidad es la del búfer de comandos, no los bytes libres de VRAM física.

Ejemplo de uso

c
__vramq_copy(0x2040,source_bytes,255);
source_bytes[0]=3; // The executor must see this change through its retained pointer.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña capacidad, punteros retenidos, reinicios independientes y ejecución directa o por NMI. Se espera CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 y FAILED CHECKS 000. Seis registros ocupan 30 bytes y realizan 512 escrituras de un byte. OVERFLOW SEEN conserva el rechazo deliberado anterior; no indica que el estado final siga desbordado.

La copia azul empieza en el píxel (0,16) y el relleno verde en (0,88). Cada área ocupa siete filas completas de 256 píxeles y 248 píxeles de la octava: 255 casillas en total. La última casilla de 8 × 8 queda negra. La copia repite patrones sólidos, de mitad izquierda y triangulares; el primero es triangular porque se modificó el origen tras encolar. El relleno sigue sólido aunque cambie la variable del llamador. Se comprueban forma y color.

En y=160 deben aparecer un cuadrado rojo sólido en x=16, una mitad izquierda roja en x=224 y un cuadrado rojo sólido en x=240. El primero sobrevive a registros de longitud cero; el central prueba el consumo por NMI; el último, un put. La casilla cancelada en (248,160) queda negra. Las transferencias largas se ejecutan con renderizado y NMI apagados; solo la escritura final pequeña usa NMI. Es evidencia de emulador NROM, no de hardware real.

__vramq_copy: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:150

Notas de la declaración (original)
Retain the source address for later consumption; keep its storage and bank mapping valid.
__vramq_exec

Ejecuta inmediatamente una cola confirmada, en orden. Si no está preparada, vuelve sin modificarla. Al terminar borra longitud y disponibilidad, pero conserva el desbordamiento. No espera un momento seguro para la PPU, limita la transferencia ni restaura el desplazamiento; debe hacerlo quien llama.

Sintaxis

c
void __vramq_exec(void);

Valor devuelto

Ninguno (void).

Condiciones y límites

Dirección PPU en bytes (0x0000–0x3FFF), no un puntero de CPU. El destino debe admitir escritura: modificar patrones requiere CHR RAM, no CHR ROM. Se aplican el mapeo y el mirroring de la PPU. Para copias y rellenos contiguos, selecciona incremento 1 en PPUCTRL. Ejecuta en un periodo seguro y restablece después el desplazamiento; el ejecutor escribe PPUADDR y PPUDATA sin restaurarlo.

FC cuenta bytes de comandos codificados en un búfer intrínseco fijo de 128 bytes. Una escritura de tile ocupa 4 bytes, un relleno 5 y una copia por puntero 6, incluidos los metadatos. NMI puede consumir una cola preparada; examínala y constrúyela antes de confirmar, coordinando productor y consumidor.

Estas intrínsecas usan el mismo búfer que lib/vram.c. Las funciones nes_vram_queue_* de runtime.c gestionan otra cola con un formato distinto. Vaciar o consultar una no vacía ni mide la otra.

Coordina las fases de construcción y ejecución de la cola. No añadas durante su ejecución en una interrupción, alteres sus contadores directamente ni sobrescribas por accidente los datos retenidos. Estas API no ofrecen bloqueo ni doble búfer. La capacidad es la del búfer de comandos, no los bytes libres de VRAM física.

Ejemplo de uso

c
// Rendering is stopped and the PPU increment is 1.
__vramq_exec();
after_exec=__vramq_len();
if (after_exec!=0 || __vramq_overflow()!=0) failures++;

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña capacidad, punteros retenidos, reinicios independientes y ejecución directa o por NMI. Se espera CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 y FAILED CHECKS 000. Seis registros ocupan 30 bytes y realizan 512 escrituras de un byte. OVERFLOW SEEN conserva el rechazo deliberado anterior; no indica que el estado final siga desbordado.

La copia azul empieza en el píxel (0,16) y el relleno verde en (0,88). Cada área ocupa siete filas completas de 256 píxeles y 248 píxeles de la octava: 255 casillas en total. La última casilla de 8 × 8 queda negra. La copia repite patrones sólidos, de mitad izquierda y triangulares; el primero es triangular porque se modificó el origen tras encolar. El relleno sigue sólido aunque cambie la variable del llamador. Se comprueban forma y color.

En y=160 deben aparecer un cuadrado rojo sólido en x=16, una mitad izquierda roja en x=224 y un cuadrado rojo sólido en x=240. El primero sobrevive a registros de longitud cero; el central prueba el consumo por NMI; el último, un put. La casilla cancelada en (248,160) queda negra. Las transferencias largas se ejecutan con renderizado y NMI apagados; solo la escritura final pequeña usa NMI. Es evidencia de emulador NROM, no de hardware real.

__vramq_exec: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:156

Notas de la declaración (original)
Execute a ready queue directly; committing and safe PPU timing are caller responsibilities.
__vramq_fill

Añade un registro de cinco bytes que repite el byte value en len escrituras PPU. El valor queda guardado; cambiar después la variable del llamador no altera el relleno encolado. Repite un byte, no un patrón gráfico de varios bytes.

Sintaxis

c
void __vramq_fill(u16 ppu_addr, u8 value, u8 len);

Parámetros

ppu_addr

Dirección PPU en bytes (0x0000–0x3FFF), no un puntero de CPU. El destino debe admitir escritura: modificar patrones requiere CHR RAM, no CHR ROM. Se aplican el mapeo y el mirroring de la PPU. Para copias y rellenos contiguos, selecciona incremento 1 en PPUCTRL. Ejecuta en un periodo seguro y restablece después el desplazamiento; el ejecutor escribe PPUADDR y PPUDATA sin restaurarlo.

value

Un byte de 0 a 255 que se repite. Su efecto visible depende de si el destino contiene índices de tiles, planos de píxeles u otra estructura de bytes.

len

Número de bytes, de 0 a 255. Un registro maneja todo el recuento; no se divide en tramos de 127 bytes. Cero sigue ocupando un registro de copia o relleno. Al ejecutarlo se establece la dirección PPU, pero no se escribe en PPUDATA. 256 queda fuera del intervalo admitido.

Valor devuelto

Ninguno (void).

Condiciones y límites

La inserción no devuelve un valor de éxito. Si no cabe todo el registro, activa __vramq_overflow() y conserva registros y longitud. Se acepta un ajuste exacto. Un indicador previo no impide añadir si hay espacio; atiéndelo o reinícialo antes de juzgar un lote nuevo. Varias inserciones no forman una transacción.

FC cuenta bytes de comandos codificados en un búfer intrínseco fijo de 128 bytes. Una escritura de tile ocupa 4 bytes, un relleno 5 y una copia por puntero 6, incluidos los metadatos. NMI puede consumir una cola preparada; examínala y constrúyela antes de confirmar, coordinando productor y consumidor.

Estas intrínsecas usan el mismo búfer que lib/vram.c. Las funciones nes_vram_queue_* de runtime.c gestionan otra cola con un formato distinto. Vaciar o consultar una no vacía ni mide la otra.

Coordina las fases de construcción y ejecución de la cola. No añadas durante su ejecución en una interrupción, alteres sus contadores directamente ni sobrescribas por accidente los datos retenidos. Estas API no ofrecen bloqueo ni doble búfer. La capacidad es la del búfer de comandos, no los bytes libres de VRAM física.

Ejemplo de uso

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.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña capacidad, punteros retenidos, reinicios independientes y ejecución directa o por NMI. Se espera CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 y FAILED CHECKS 000. Seis registros ocupan 30 bytes y realizan 512 escrituras de un byte. OVERFLOW SEEN conserva el rechazo deliberado anterior; no indica que el estado final siga desbordado.

La copia azul empieza en el píxel (0,16) y el relleno verde en (0,88). Cada área ocupa siete filas completas de 256 píxeles y 248 píxeles de la octava: 255 casillas en total. La última casilla de 8 × 8 queda negra. La copia repite patrones sólidos, de mitad izquierda y triangulares; el primero es triangular porque se modificó el origen tras encolar. El relleno sigue sólido aunque cambie la variable del llamador. Se comprueban forma y color.

En y=160 deben aparecer un cuadrado rojo sólido en x=16, una mitad izquierda roja en x=224 y un cuadrado rojo sólido en x=240. El primero sobrevive a registros de longitud cero; el central prueba el consumo por NMI; el último, un put. La casilla cancelada en (248,160) queda negra. Las transferencias largas se ejecutan con renderizado y NMI apagados; solo la escritura final pequeña usa NMI. Es evidencia de emulador NROM, no de hardware real.

__vramq_fill: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:152

Notas de la declaración (original)
Append a repeated-byte update; the source value is stored in the record.
__vramq_len

Devuelve el espacio del búfer ocupado por los comandos pendientes, no los bytes que se escribirán en memoria de vídeo.

Sintaxis

c
u8 __vramq_len(void);

Valor devuelto

Un byte sin signo en las unidades de la plataforma indicadas aquí. Leerlo no modifica ni ejecuta la cola.

Condiciones y límites

FC cuenta bytes de comandos codificados en un búfer intrínseco fijo de 128 bytes. Una escritura de tile ocupa 4 bytes, un relleno 5 y una copia por puntero 6, incluidos los metadatos. NMI puede consumir una cola preparada; examínala y constrúyela antes de confirmar, coordinando productor y consumidor.

Estas intrínsecas usan el mismo búfer que lib/vram.c. Las funciones nes_vram_queue_* de runtime.c gestionan otra cola con un formato distinto. Vaciar o consultar una no vacía ni mide la otra.

Coordina las fases de construcción y ejecución de la cola. No añadas durante su ejecución en una interrupción, alteres sus contadores directamente ni sobrescribas por accidente los datos retenidos. Estas API no ofrecen bloqueo ni doble búfer. La capacidad es la del búfer de comandos, no los bytes libres de VRAM física.

Ejemplo de uso

c
encoded=__vramq_len();
if (encoded!=30 || capacity-encoded!=98) failures++;
// 4+6+5+6+5+4 encoded bytes describe 512 actual byte writes.

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña capacidad, punteros retenidos, reinicios independientes y ejecución directa o por NMI. Se espera CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 y FAILED CHECKS 000. Seis registros ocupan 30 bytes y realizan 512 escrituras de un byte. OVERFLOW SEEN conserva el rechazo deliberado anterior; no indica que el estado final siga desbordado.

La copia azul empieza en el píxel (0,16) y el relleno verde en (0,88). Cada área ocupa siete filas completas de 256 píxeles y 248 píxeles de la octava: 255 casillas en total. La última casilla de 8 × 8 queda negra. La copia repite patrones sólidos, de mitad izquierda y triangulares; el primero es triangular porque se modificó el origen tras encolar. El relleno sigue sólido aunque cambie la variable del llamador. Se comprueban forma y color.

En y=160 deben aparecer un cuadrado rojo sólido en x=16, una mitad izquierda roja en x=224 y un cuadrado rojo sólido en x=240. El primero sobrevive a registros de longitud cero; el central prueba el consumo por NMI; el último, un put. La casilla cancelada en (248,160) queda negra. Las transferencias largas se ejecutan con renderizado y NMI apagados; solo la escritura final pequeña usa NMI. Es evidencia de emulador NROM, no de hardware real.

__vramq_len: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:158

Notas de la declaración (original)
Return encoded bytes currently occupied, not transfer payload bytes or command count.
__vramq_overflow

Lee la marca de desbordamiento sin borrarla; puede reflejar un fallo anterior.

Sintaxis

c
u8 __vramq_overflow(void);

Valor devuelto

0 si no hay desbordamiento memorizado; 1 tras rechazar una inserción hasta un reinicio explícito. Ejecutar la cola no lo borra.

Condiciones y límites

FC cuenta bytes de comandos codificados en un búfer intrínseco fijo de 128 bytes. Una escritura de tile ocupa 4 bytes, un relleno 5 y una copia por puntero 6, incluidos los metadatos. NMI puede consumir una cola preparada; examínala y constrúyela antes de confirmar, coordinando productor y consumidor.

Estas intrínsecas usan el mismo búfer que lib/vram.c. Las funciones nes_vram_queue_* de runtime.c gestionan otra cola con un formato distinto. Vaciar o consultar una no vacía ni mide la otra.

Coordina las fases de construcción y ejecución de la cola. No añadas durante su ejecución en una interrupción, alteres sus contadores directamente ni sobrescribas por accidente los datos retenidos. Estas API no ofrecen bloqueo ni doble búfer. La capacidad es la del búfer de comandos, no los bytes libres de VRAM física.

Ejemplo de uso

c
overflow_seen=__vramq_overflow();
if (overflow_seen!=1 || __vramq_overflow()!=1) failures++;

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña capacidad, punteros retenidos, reinicios independientes y ejecución directa o por NMI. Se espera CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 y FAILED CHECKS 000. Seis registros ocupan 30 bytes y realizan 512 escrituras de un byte. OVERFLOW SEEN conserva el rechazo deliberado anterior; no indica que el estado final siga desbordado.

La copia azul empieza en el píxel (0,16) y el relleno verde en (0,88). Cada área ocupa siete filas completas de 256 píxeles y 248 píxeles de la octava: 255 casillas en total. La última casilla de 8 × 8 queda negra. La copia repite patrones sólidos, de mitad izquierda y triangulares; el primero es triangular porque se modificó el origen tras encolar. El relleno sigue sólido aunque cambie la variable del llamador. Se comprueban forma y color.

En y=160 deben aparecer un cuadrado rojo sólido en x=16, una mitad izquierda roja en x=224 y un cuadrado rojo sólido en x=240. El primero sobrevive a registros de longitud cero; el central prueba el consumo por NMI; el último, un put. La casilla cancelada en (248,160) queda negra. Las transferencias largas se ejecutan con renderizado y NMI apagados; solo la escritura final pequeña usa NMI. Es evidencia de emulador NROM, no de hardware real.

__vramq_overflow: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:160

Notas de la declaración (original)
Read the latched capacity failure; reading does not reset it.
__vramq_put

Añade una dirección PPU y un valor de un byte en un registro de cuatro bytes. El valor se copia ahora a la cola y se escribe al ejecutarla. Sirve para actualizar una casilla de la tabla de nombres u otro byte escribible de la PPU.

Sintaxis

c
void __vramq_put(u16 ppu_addr, u8 value);

Parámetros

ppu_addr

Dirección PPU en bytes (0x0000–0x3FFF), no un puntero de CPU. El destino debe admitir escritura: modificar patrones requiere CHR RAM, no CHR ROM. Se aplican el mapeo y el mirroring de la PPU. Para copias y rellenos contiguos, selecciona incremento 1 en PPUCTRL. Ejecuta en un periodo seguro y restablece después el desplazamiento; el ejecutor escribe PPUADDR y PPUDATA sin restaurarlo.

value

Un byte de 0 a 255 que se repite. Su efecto visible depende de si el destino contiene índices de tiles, planos de píxeles u otra estructura de bytes.

Valor devuelto

Ninguno (void).

Condiciones y límites

La inserción no devuelve un valor de éxito. Si no cabe todo el registro, activa __vramq_overflow() y conserva registros y longitud. Se acepta un ajuste exacto. Un indicador previo no impide añadir si hay espacio; atiéndelo o reinícialo antes de juzgar un lote nuevo. Varias inserciones no forman una transacción.

FC cuenta bytes de comandos codificados en un búfer intrínseco fijo de 128 bytes. Una escritura de tile ocupa 4 bytes, un relleno 5 y una copia por puntero 6, incluidos los metadatos. NMI puede consumir una cola preparada; examínala y constrúyela antes de confirmar, coordinando productor y consumidor.

Estas intrínsecas usan el mismo búfer que lib/vram.c. Las funciones nes_vram_queue_* de runtime.c gestionan otra cola con un formato distinto. Vaciar o consultar una no vacía ni mide la otra.

Coordina las fases de construcción y ejecución de la cola. No añadas durante su ejecución en una interrupción, alteres sus contadores directamente ni sobrescribas por accidente los datos retenidos. Estas API no ofrecen bloqueo ni doble búfer. La capacidad es la del búfer de comandos, no los bytes libres de VRAM física.

Ejemplo de uso

c
__vramq_put(0x2282,1); // Red 8x8 sentinel at pixel (16,160).

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

El programa enseña capacidad, punteros retenidos, reinicios independientes y ejecución directa o por NMI. Se espera CAPACITY 128, ENCODED BYTES 030, AFTER EXEC 000, OVERFLOW SEEN 001 y FAILED CHECKS 000. Seis registros ocupan 30 bytes y realizan 512 escrituras de un byte. OVERFLOW SEEN conserva el rechazo deliberado anterior; no indica que el estado final siga desbordado.

La copia azul empieza en el píxel (0,16) y el relleno verde en (0,88). Cada área ocupa siete filas completas de 256 píxeles y 248 píxeles de la octava: 255 casillas en total. La última casilla de 8 × 8 queda negra. La copia repite patrones sólidos, de mitad izquierda y triangulares; el primero es triangular porque se modificó el origen tras encolar. El relleno sigue sólido aunque cambie la variable del llamador. Se comprueban forma y color.

En y=160 deben aparecer un cuadrado rojo sólido en x=16, una mitad izquierda roja en x=224 y un cuadrado rojo sólido en x=240. El primero sobrevive a registros de longitud cero; el central prueba el consumo por NMI; el último, un put. La casilla cancelada en (248,160) queda negra. Las transferencias largas se ejecutan con renderizado y NMI apagados; solo la escritura final pequeña usa NMI. Es evidencia de emulador NROM, no de hardware real.

__vramq_put: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:148

Notas de la declaración (original)
Append one address/value update; use the overflow latch to detect capacity failure.
__xy_in_rect

Comprueba si está dentro de un rectángulo, incluidos los bordes superior e izquierdo y excluidos el inferior y el derecho.

Sintaxis

c
u8 __xy_in_rect(u8 x, u8 y, u8 rx, u8 ry, u8 rw, u8 rh);

Parámetros

x

X del punto, de 0 a 255.

y

Y del punto, de 0 a 255.

rx

Borde izquierdo del rectángulo, de 0 a 255.

ry

Borde superior del rectángulo, de 0 a 255.

rw

Ancho de 0 a 255.

rh

Alto de 0 a 255.

Valor devuelto

Devuelve 1 si está dentro y 0 si no. Un ancho o alto de cero representa un rectángulo vacío.

Condiciones y límites

Usa diferencias de coordenadas: un límite derecho superior a 255 no se desborda hacia la izquierda.

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

Prueba el interior, los límites, un punto exterior y ancho cero: INSIDE=1, RIGHT EDGE=0, LEFT EDGE=0, HIGH X=1. Verifica la distancia 15, la distancia desbordada 254 y los índices de array 64/65280.

__xy_in_rect: NROM
NROM

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:31

Notas de la declaración (original)
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

Lector de luz del puerto 2 utilizado por defecto. Llama a la misma rutina que __zapper_light2().

Sintaxis

c
u8 __zapper_light(void);

Valor devuelto

1 si el bit 3 de $4017 está a cero; en caso contrario, 0. No lee el puerto 1.

Condiciones y límites

Estas API acceden directamente a $4016/$4017. No intercales operaciones de mando, teclado o serie mediante interrupciones en los mismos puertos. También pueden afectar a las posiciones de desplazamiento del controlador. El ejemplo deshabilita NMI mientras captura entradas y la habilita al inicializar la pantalla.

Lee la señal instantánea. No genera strobe, espera un fotograma, detecta flancos, localiza la puntería ni decide impactos. Las llamadas de valor bruto y de gatillo/luz leen el registro en instantes propios, por lo que pueden diferir mientras cambia la señal.

La entrada de luz es activa en bajo: el bit 3 a cero devuelve uno. Esta conversión no detecta si hay una pistola conectada; un mando o una entrada desconectada también pueden dar uno. Dibujar un objetivo en pantalla no convierte esto en una API de detección de impactos.

La conversión de bits también se comprueba en una ROM de prueba propia, redirigiendo las lecturas de registros a valores de prueba en RAM. Se cubren las combinaciones 00/08/10/18 con otros bits activados. Esta prueba unitaria es independiente de la ejecución del ejemplo sin modificar y del comportamiento de un periférico real.

Estas imágenes ejecutan las ROM descargables sin cambios en KUROSAKI. Su bus de entrada modela mandos, pero no señales de teclado, pistola de luz ni adaptador serie. Los bits externos se leen como cero, por lo que las capturas no verifican esos dispositivos. No se han probado teclas físicas, detección óptica ni comunicación.

NESdev: Zapper

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.

__zapper_light: ZAPPER-DEFAULT — En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:253

Notas de la declaración (original)
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

Lee la señal de luz del puerto 1 ($4016) y convierte la entrada activa a nivel bajo en un 1 cuando se detecta luz.

Sintaxis

c
u8 __zapper_light1(void);

Valor devuelto

1 si el bit 3 de $4016 está a cero; 0 si está activado.

Condiciones y límites

Estas API acceden directamente a $4016/$4017. No intercales operaciones de mando, teclado o serie mediante interrupciones en los mismos puertos. También pueden afectar a las posiciones de desplazamiento del controlador. El ejemplo deshabilita NMI mientras captura entradas y la habilita al inicializar la pantalla.

Lee la señal instantánea. No genera strobe, espera un fotograma, detecta flancos, localiza la puntería ni decide impactos. Las llamadas de valor bruto y de gatillo/luz leen el registro en instantes propios, por lo que pueden diferir mientras cambia la señal.

La entrada de luz es activa en bajo: el bit 3 a cero devuelve uno. Esta conversión no detecta si hay una pistola conectada; un mando o una entrada desconectada también pueden dar uno. Dibujar un objetivo en pantalla no convierte esto en una API de detección de impactos.

La conversión de bits también se comprueba en una ROM de prueba propia, redirigiendo las lecturas de registros a valores de prueba en RAM. Se cubren las combinaciones 00/08/10/18 con otros bits activados. Esta prueba unitaria es independiente de la ejecución del ejemplo sin modificar y del comportamiento de un periférico real.

Estas imágenes ejecutan las ROM descargables sin cambios en KUROSAKI. Su bus de entrada modela mandos, pero no señales de teclado, pistola de luz ni adaptador serie. Los bits externos se leen como cero, por lo que las capturas no verifican esos dispositivos. No se han probado teclas físicas, detección óptica ni comunicación.

NESdev: Zapper

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.

__zapper_light1: ZAPPER-DEFAULT — En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:247

Notas de la declaración (original)
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

Lee la señal de luz del puerto 2 ($4017) y convierte la entrada activa a nivel bajo en un 1 cuando se detecta luz.

Sintaxis

c
u8 __zapper_light2(void);

Valor devuelto

1 si el bit 3 de $4017 está a cero; 0 si está activado.

Condiciones y límites

Estas API acceden directamente a $4016/$4017. No intercales operaciones de mando, teclado o serie mediante interrupciones en los mismos puertos. También pueden afectar a las posiciones de desplazamiento del controlador. El ejemplo deshabilita NMI mientras captura entradas y la habilita al inicializar la pantalla.

Lee la señal instantánea. No genera strobe, espera un fotograma, detecta flancos, localiza la puntería ni decide impactos. Las llamadas de valor bruto y de gatillo/luz leen el registro en instantes propios, por lo que pueden diferir mientras cambia la señal.

La entrada de luz es activa en bajo: el bit 3 a cero devuelve uno. Esta conversión no detecta si hay una pistola conectada; un mando o una entrada desconectada también pueden dar uno. Dibujar un objetivo en pantalla no convierte esto en una API de detección de impactos.

La conversión de bits también se comprueba en una ROM de prueba propia, redirigiendo las lecturas de registros a valores de prueba en RAM. Se cubren las combinaciones 00/08/10/18 con otros bits activados. Esta prueba unitaria es independiente de la ejecución del ejemplo sin modificar y del comportamiento de un periférico real.

Estas imágenes ejecutan las ROM descargables sin cambios en KUROSAKI. Su bus de entrada modela mandos, pero no señales de teclado, pistola de luz ni adaptador serie. Los bits externos se leen como cero, por lo que las capturas no verifican esos dispositivos. No se han probado teclas físicas, detección óptica ni comunicación.

NESdev: Zapper

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.

__zapper_light2: ZAPPER-DEFAULT — En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:249

Notas de la declaración (original)
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

Lee juntos los dos bits de la pistola de luz del puerto 1 ($4016) para interpretar el gatillo y la luz a partir de una misma lectura del registro.

Sintaxis

c
u8 __zapper_raw1(void);

Valor devuelto

Devuelve $4016 & 0x18: 0x00, 0x08, 0x10 o 0x18. El bit 4 corresponde al gatillo; el bit 3 es la señal sin procesar de ausencia de luz.

Condiciones y límites

Estas API acceden directamente a $4016/$4017. No intercales operaciones de mando, teclado o serie mediante interrupciones en los mismos puertos. También pueden afectar a las posiciones de desplazamiento del controlador. El ejemplo deshabilita NMI mientras captura entradas y la habilita al inicializar la pantalla.

Lee la señal instantánea. No genera strobe, espera un fotograma, detecta flancos, localiza la puntería ni decide impactos. Las llamadas de valor bruto y de gatillo/luz leen el registro en instantes propios, por lo que pueden diferir mientras cambia la señal.

La entrada de luz es activa en bajo: el bit 3 a cero devuelve uno. Esta conversión no detecta si hay una pistola conectada; un mando o una entrada desconectada también pueden dar uno. Dibujar un objetivo en pantalla no convierte esto en una API de detección de impactos.

La conversión de bits también se comprueba en una ROM de prueba propia, redirigiendo las lecturas de registros a valores de prueba en RAM. Se cubren las combinaciones 00/08/10/18 con otros bits activados. Esta prueba unitaria es independiente de la ejecución del ejemplo sin modificar y del comportamiento de un periférico real.

Estas imágenes ejecutan las ROM descargables sin cambios en KUROSAKI. Su bus de entrada modela mandos, pero no señales de teclado, pistola de luz ni adaptador serie. Los bits externos se leen como cero, por lo que las capturas no verifican esos dispositivos. No se han probado teclas físicas, detección óptica ni comunicación.

NESdev: Zapper

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.

__zapper_raw1: ZAPPER-DEFAULT — En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:239

Notas de la declaración (original)
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

Lee juntos los dos bits de la pistola de luz del puerto 2 ($4017) para interpretar el gatillo y la luz a partir de una misma lectura del registro.

Sintaxis

c
u8 __zapper_raw2(void);

Valor devuelto

Devuelve $4017 & 0x18: 0x00, 0x08, 0x10 o 0x18. El bit 4 corresponde al gatillo; el bit 3 es la señal sin procesar de ausencia de luz.

Condiciones y límites

Estas API acceden directamente a $4016/$4017. No intercales operaciones de mando, teclado o serie mediante interrupciones en los mismos puertos. También pueden afectar a las posiciones de desplazamiento del controlador. El ejemplo deshabilita NMI mientras captura entradas y la habilita al inicializar la pantalla.

Lee la señal instantánea. No genera strobe, espera un fotograma, detecta flancos, localiza la puntería ni decide impactos. Las llamadas de valor bruto y de gatillo/luz leen el registro en instantes propios, por lo que pueden diferir mientras cambia la señal.

La entrada de luz es activa en bajo: el bit 3 a cero devuelve uno. Esta conversión no detecta si hay una pistola conectada; un mando o una entrada desconectada también pueden dar uno. Dibujar un objetivo en pantalla no convierte esto en una API de detección de impactos.

La conversión de bits también se comprueba en una ROM de prueba propia, redirigiendo las lecturas de registros a valores de prueba en RAM. Se cubren las combinaciones 00/08/10/18 con otros bits activados. Esta prueba unitaria es independiente de la ejecución del ejemplo sin modificar y del comportamiento de un periférico real.

Estas imágenes ejecutan las ROM descargables sin cambios en KUROSAKI. Su bus de entrada modela mandos, pero no señales de teclado, pistola de luz ni adaptador serie. Los bits externos se leen como cero, por lo que las capturas no verifican esos dispositivos. No se han probado teclas físicas, detección óptica ni comunicación.

NESdev: Zapper

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.

__zapper_raw2: ZAPPER-DEFAULT — En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:241

Notas de la declaración (original)
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

Lector del gatillo del puerto 2 utilizado por defecto. Llama a la misma rutina que __zapper_trigger2().

Sintaxis

c
u8 __zapper_trigger(void);

Valor devuelto

El bit 4 de $4017 normalizado a 0 o 1. Nunca cambia automáticamente al puerto 1.

Condiciones y límites

Estas API acceden directamente a $4016/$4017. No intercales operaciones de mando, teclado o serie mediante interrupciones en los mismos puertos. También pueden afectar a las posiciones de desplazamiento del controlador. El ejemplo deshabilita NMI mientras captura entradas y la habilita al inicializar la pantalla.

Lee la señal instantánea. No genera strobe, espera un fotograma, detecta flancos, localiza la puntería ni decide impactos. Las llamadas de valor bruto y de gatillo/luz leen el registro en instantes propios, por lo que pueden diferir mientras cambia la señal.

El resultado es el estado lógico de la línea del gatillo. No garantiza que siga completamente apretado y el comportamiento del interruptor depende del dispositivo. Para generar un disparo en el juego, compara con la lectura anterior y detecta la transición deseada.

La conversión de bits también se comprueba en una ROM de prueba propia, redirigiendo las lecturas de registros a valores de prueba en RAM. Se cubren las combinaciones 00/08/10/18 con otros bits activados. Esta prueba unitaria es independiente de la ejecución del ejemplo sin modificar y del comportamiento de un periférico real.

Estas imágenes ejecutan las ROM descargables sin cambios en KUROSAKI. Su bus de entrada modela mandos, pero no señales de teclado, pistola de luz ni adaptador serie. Los bits externos se leen como cero, por lo que las capturas no verifican esos dispositivos. No se han probado teclas físicas, detección óptica ni comunicación.

NESdev: Zapper

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.

__zapper_trigger: ZAPPER-DEFAULT — En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:251

Notas de la declaración (original)
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

Lee la señal del gatillo del puerto 1 ($4016) y la normaliza a 0 o 1.

Sintaxis

c
u8 __zapper_trigger1(void);

Valor devuelto

1 si el bit 4 de $4016 está activado; en caso contrario, 0.

Condiciones y límites

Estas API acceden directamente a $4016/$4017. No intercales operaciones de mando, teclado o serie mediante interrupciones en los mismos puertos. También pueden afectar a las posiciones de desplazamiento del controlador. El ejemplo deshabilita NMI mientras captura entradas y la habilita al inicializar la pantalla.

Lee la señal instantánea. No genera strobe, espera un fotograma, detecta flancos, localiza la puntería ni decide impactos. Las llamadas de valor bruto y de gatillo/luz leen el registro en instantes propios, por lo que pueden diferir mientras cambia la señal.

El resultado es el estado lógico de la línea del gatillo. No garantiza que siga completamente apretado y el comportamiento del interruptor depende del dispositivo. Para generar un disparo en el juego, compara con la lectura anterior y detecta la transición deseada.

La conversión de bits también se comprueba en una ROM de prueba propia, redirigiendo las lecturas de registros a valores de prueba en RAM. Se cubren las combinaciones 00/08/10/18 con otros bits activados. Esta prueba unitaria es independiente de la ejecución del ejemplo sin modificar y del comportamiento de un periférico real.

Estas imágenes ejecutan las ROM descargables sin cambios en KUROSAKI. Su bus de entrada modela mandos, pero no señales de teclado, pistola de luz ni adaptador serie. Los bits externos se leen como cero, por lo que las capturas no verifican esos dispositivos. No se han probado teclas físicas, detección óptica ni comunicación.

NESdev: Zapper

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.

__zapper_trigger1: ZAPPER-DEFAULT — En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:243

Notas de la declaración (original)
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

Lee la señal del gatillo del puerto 2 ($4017) y la normaliza a 0 o 1.

Sintaxis

c
u8 __zapper_trigger2(void);

Valor devuelto

1 si el bit 4 de $4017 está activado; en caso contrario, 0.

Condiciones y límites

Estas API acceden directamente a $4016/$4017. No intercales operaciones de mando, teclado o serie mediante interrupciones en los mismos puertos. También pueden afectar a las posiciones de desplazamiento del controlador. El ejemplo deshabilita NMI mientras captura entradas y la habilita al inicializar la pantalla.

Lee la señal instantánea. No genera strobe, espera un fotograma, detecta flancos, localiza la puntería ni decide impactos. Las llamadas de valor bruto y de gatillo/luz leen el registro en instantes propios, por lo que pueden diferir mientras cambia la señal.

El resultado es el estado lógico de la línea del gatillo. No garantiza que siga completamente apretado y el comportamiento del interruptor depende del dispositivo. Para generar un disparo en el juego, compara con la lectura anterior y detecta la transición deseada.

La conversión de bits también se comprueba en una ROM de prueba propia, redirigiendo las lecturas de registros a valores de prueba en RAM. Se cubren las combinaciones 00/08/10/18 con otros bits activados. Esta prueba unitaria es independiente de la ejecución del ejemplo sin modificar y del comportamiento de un periférico real.

Estas imágenes ejecutan las ROM descargables sin cambios en KUROSAKI. Su bus de entrada modela mandos, pero no señales de teclado, pistola de luz ni adaptador serie. Los bits externos se leen como cero, por lo que las capturas no verifican esos dispositivos. No se han probado teclas físicas, detección óptica ni comunicación.

NESdev: Zapper

Ejemplo de uso

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

Este fragmento muestra la llamada. Antes de incorporarlo a un programa completo, cumple los requisitos de inicialización y de los búferes indicados.

Resultado esperado

En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.

__zapper_trigger2: ZAPPER-DEFAULT — En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.
ZAPPER-DEFAULT — En KUROSAKI, ambos puertos muestran RAW=00, TRIGGER=000 y LIGHT=001. Las llamadas sin sufijo dan los mismos valores 000/001 que el puerto 2. LIGHT=001 se debe a que el bit de entrada 3, que no está emulado, se lee como cero; no indica que se haya detectado brillo en pantalla. La pantalla también advierte LIGHT 1 IS NOT A HIT TEST.

Programa de ejemplo completo

Comando de compilación

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
Implementación y código fuente

kitaqfc/lib/intrinsics.h:245

Notas de la declaración (original)
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");
        }

Referencia de sintaxis de comandos

La siguiente ayuda se capturó para esta edición del código fuente. Consúltala para conocer los argumentos opcionales, los valores predeterminados y los valores admitidos. Sustituye los nombres de archivo ilustrativos por tus propios archivos. Las respuestas capturadas de las herramientas conservan su redacción original.

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.

Sintaxis de comandos para desarrolladores

Estos usos proceden de Program.DebugTools.cs y Program.VibeTools.cs. La comparación de ROM, la búsqueda de símbolos y las plantillas son subcomandos distintos de la compilación C habitual.

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]

Opciones largas reconocidas por el analizador

Los nombres se han extraído de src/cli.rs e incluyen opciones de compatibilidad y de investigación. Consulta la ayuda y el manejador correspondiente para determinar si cada opción requiere un valor.

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

Apéndice: índice de instrucciones de ensamblador

Esta tabla se extrae de src/machine/opcodes.rs. Describe los nombres internos del compilador y los formatos de operandos. Las entradas con destinos de salto o direcciones de memoria son ejemplos de sintaxis, no programas independientes. Consulta las convenciones de llamada y la generación de código para conocer los registros preservados y los cambios de indicadores.

opcodeInstrucciónFormatoEjemplo de sintaxis
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