From c39ebf5b5105e19919825d7e959f308da1b86bf8 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:07:03 +0200 Subject: [PATCH 01/27] Add zx81sd architecture for ZX81 + SD81 Booster hardware New target architecture that compiles ZX BASIC programs for the ZX81 with SD81 Booster expansion (512KB RAM, SD card, AY sound, FPGA video). Key design decisions: - Inherits zx48k runtime; only overrides files specific to SD81 hardware - Two-stage boot: stage 1 at $6000 (external BASIC loader), stage 2 at $0100 - Flat binary ORG $0000 with RST vector table ($0000-$00FF) and 8KB page layout - Spectrum HiRes mode: framebuffer at $C000 (block 6), attrs at $D800 - Sysvars mapped to $8000+ (blocks 4-5) instead of Spectrum ROM addresses - SCREEN_ADDR / SCREEN_ATTR_ADDR as RAM pointers (not EQU constants) to match the indirect addressing used by the inherited zx48k runtime - CHARS pointer initialised to font_base - 256 (Spectrum convention) - Bold/italic FLAGS2 check uses direct LD A,(FLAGS2) without PUSH/POP AF to avoid destroying the Z flag from BIT before CALL NZ New files: src/arch/zx81sd/ backend (main.py, generic.py) src/lib/arch/zx81sd/runtime/ sysvars, bootstrap, charset (INCBIN specfont.bin), print, border, pause, vsync, paging, vectors, ... Co-Authored-By: Claude Sonnet 4.6 --- src/arch/__init__.py | 1 + src/arch/zx81sd/__init__.py | 34 ++ src/arch/zx81sd/backend/__init__.py | 29 + src/arch/zx81sd/backend/generic.py | 29 + src/arch/zx81sd/backend/main.py | 202 +++++++ src/lib/arch/zx81sd/runtime/arch_config.asm | 13 + src/lib/arch/zx81sd/runtime/bootstrap.asm | 80 +++ src/lib/arch/zx81sd/runtime/border.asm | 16 + src/lib/arch/zx81sd/runtime/break.asm | 62 +++ src/lib/arch/zx81sd/runtime/charset.asm | 11 + src/lib/arch/zx81sd/runtime/error.asm | 40 ++ src/lib/arch/zx81sd/runtime/fp_calc.asm | 34 ++ src/lib/arch/zx81sd/runtime/paging.asm | 54 ++ src/lib/arch/zx81sd/runtime/pause.asm | 29 + src/lib/arch/zx81sd/runtime/pixel_addr.asm | 71 +++ src/lib/arch/zx81sd/runtime/po_gr_1.asm | 88 +++ src/lib/arch/zx81sd/runtime/print.asm | 586 ++++++++++++++++++++ src/lib/arch/zx81sd/runtime/random.asm | 102 ++++ src/lib/arch/zx81sd/runtime/specfont.bin | Bin 0 -> 768 bytes src/lib/arch/zx81sd/runtime/sysvars.asm | 57 ++ src/lib/arch/zx81sd/runtime/vectors.asm | 66 +++ src/lib/arch/zx81sd/runtime/vsync.asm | 49 ++ src/zxbpp/zxbpp.py | 8 + 23 files changed, 1661 insertions(+) create mode 100644 src/arch/zx81sd/__init__.py create mode 100644 src/arch/zx81sd/backend/__init__.py create mode 100644 src/arch/zx81sd/backend/generic.py create mode 100644 src/arch/zx81sd/backend/main.py create mode 100644 src/lib/arch/zx81sd/runtime/arch_config.asm create mode 100644 src/lib/arch/zx81sd/runtime/bootstrap.asm create mode 100644 src/lib/arch/zx81sd/runtime/border.asm create mode 100644 src/lib/arch/zx81sd/runtime/break.asm create mode 100644 src/lib/arch/zx81sd/runtime/charset.asm create mode 100644 src/lib/arch/zx81sd/runtime/error.asm create mode 100644 src/lib/arch/zx81sd/runtime/fp_calc.asm create mode 100644 src/lib/arch/zx81sd/runtime/paging.asm create mode 100644 src/lib/arch/zx81sd/runtime/pause.asm create mode 100644 src/lib/arch/zx81sd/runtime/pixel_addr.asm create mode 100644 src/lib/arch/zx81sd/runtime/po_gr_1.asm create mode 100644 src/lib/arch/zx81sd/runtime/print.asm create mode 100644 src/lib/arch/zx81sd/runtime/random.asm create mode 100644 src/lib/arch/zx81sd/runtime/specfont.bin create mode 100644 src/lib/arch/zx81sd/runtime/sysvars.asm create mode 100644 src/lib/arch/zx81sd/runtime/vectors.asm create mode 100644 src/lib/arch/zx81sd/runtime/vsync.asm diff --git a/src/arch/__init__.py b/src/arch/__init__.py index fe5ba2cf3..241f40820 100755 --- a/src/arch/__init__.py +++ b/src/arch/__init__.py @@ -13,6 +13,7 @@ __all__ = ( "zx48k", "zxnext", + "zx81sd", ) AVAILABLE_ARCHITECTURES = __all__ diff --git a/src/arch/zx81sd/__init__.py b/src/arch/zx81sd/__init__.py new file mode 100644 index 000000000..f4ec43c0c --- /dev/null +++ b/src/arch/zx81sd/__init__.py @@ -0,0 +1,34 @@ +# -------------------------------------------------------------------- +# SPDX-License-Identifier: AGPL-3.0-or-later +# ZX81 + SD81 Booster architecture for Boriel ZX BASIC compiler +# Inherits from zx48k, overriding the backend for SD81 hardware. +# -------------------------------------------------------------------- + +import src.api.global_ +from src.api.constants import TYPE +from src.arch.z80 import ( + FunctionTranslator, + Translator, + VarTranslator, + beep, + optimizer, # noqa +) +from src.arch.zx81sd import backend # noqa + +__all__ = ( + "FunctionTranslator", + "Translator", + "VarTranslator", + "beep", +) + +# ----------------------------------------- +# Arch initialization setup (same as zx48k) +# ----------------------------------------- +src.api.global_.PARAM_ALIGN = 2 +src.api.global_.BOUND_TYPE = TYPE.uinteger +src.api.global_.SIZE_TYPE = TYPE.ubyte +src.api.global_.PTR_TYPE = TYPE.uinteger +src.api.global_.STR_INDEX_TYPE = TYPE.uinteger +src.api.global_.MIN_STRSLICE_IDX = 0 +src.api.global_.MAX_STRSLICE_IDX = 65534 diff --git a/src/arch/zx81sd/backend/__init__.py b/src/arch/zx81sd/backend/__init__.py new file mode 100644 index 000000000..792f01277 --- /dev/null +++ b/src/arch/zx81sd/backend/__init__.py @@ -0,0 +1,29 @@ +# -------------------------------------------------------------------- +# SPDX-License-Identifier: AGPL-3.0-or-later +# ZX81 + SD81 Booster backend for Boriel ZX BASIC compiler +# -------------------------------------------------------------------- + +from src.arch.z80.backend import ( + HI16, + INITS, + LO16, + MEMINITS, + REQUIRES, + TMP_COUNTER, + TMP_STORAGES, + Float, +) + +from .main import Backend + +__all__ = [ + "HI16", + "INITS", + "LO16", + "MEMINITS", + "REQUIRES", + "TMP_COUNTER", + "TMP_STORAGES", + "Backend", + "Float", +] diff --git a/src/arch/zx81sd/backend/generic.py b/src/arch/zx81sd/backend/generic.py new file mode 100644 index 000000000..196556ddc --- /dev/null +++ b/src/arch/zx81sd/backend/generic.py @@ -0,0 +1,29 @@ +# -------------------------------------------------------------------- +# SPDX-License-Identifier: AGPL-3.0-or-later +# ZX81 + SD81 Booster — handler del opcode END +# -------------------------------------------------------------------- + +from src.arch.interface.quad import Quad +from src.arch.z80.backend import Bits16, common + + +def _end(ins: Quad): + """Secuencia de fin de programa para ZX81 + SD81 Booster. + + No hay ROM ni BASIC al que volver: detiene la CPU de forma segura. + Si END aparece varias veces en el programa (salidas anticipadas), + los casos posteriores generan un JP al primer bloque END emitido. + """ + output = Bits16.get_oper(ins[1]) + output.append("ld b, h") + output.append("ld c, l") + + if common.FLAG_end_emitted: + return output + [f"jp {common.END_LABEL}"] + + common.FLAG_end_emitted = True + + output.append(f"{common.END_LABEL}:") + output.append("di") + output.append("halt") + return output diff --git a/src/arch/zx81sd/backend/main.py b/src/arch/zx81sd/backend/main.py new file mode 100644 index 000000000..940279407 --- /dev/null +++ b/src/arch/zx81sd/backend/main.py @@ -0,0 +1,202 @@ +# -------------------------------------------------------------------- +# SPDX-License-Identifier: AGPL-3.0-or-later +# ZX81 + SD81 Booster backend — program prologue / epilogue +# -------------------------------------------------------------------- + +from src.api.config import OPTIONS +from src.api.options import Action +from src.arch.z80.backend import Backend as Z80Backend +from src.arch.z80.backend import ICInfo, common +from src.arch.z80.backend.icinstruction import ICInstruction +from src.arch.z80.backend.runtime import NAMESPACE +from src.arch.z80.peephole import engine + +from .generic import _end + +# --------------------------------------------------------------------------- +# Constantes de arquitectura ZX81 + SD81 Booster +# --------------------------------------------------------------------------- + +# Mapa de memoria (flat, ORG $0000): +# $0000-$00FF vectors.asm — vectores RST + relleno hasta $0100 +# $0100-$0FFF stage 2 bootstrap (prólogo) + rutinas de sistema +# $1000-$7FFF runtime ZX BASIC + código del usuario (28 KB) +# $8000-$80FF sysvars del runtime +# $8100-$BFFF heap + datos del usuario (~15.75 KB) +# $C000-$D7FF bitmap pantalla Spectrum (bloque 6, página dedicada) +# $D800-$DAFF atributos pantalla +# $E000-$FFFF bloque 7 — banking de datos (mapas, sprites...) + +_ORG = 0x0000 # el binario comienza en $0000 (vectors.asm lo rellena hasta $0100) +_STAGE2_ENTRY = 0x0100 # punto de entrada del stage 2 bootstrap + +_HEAP_ADDR = 0x8100 # heap en zona de datos ($8100-$BFFF) +_HEAP_SIZE = 0x3EFF # ~15.75 KB + +# Páginas SD81 asignadas a cada bloque (las carga el BASIC antes del salto) +# Página 8 → bloque 0 ($0000-$1FFF) ← el stage 1 solo mapea este +# Página 9 → bloque 1 ($2000-$3FFF) ┐ +# Página 10 → bloque 2 ($4000-$5FFF) │ el stage 2 (aquí) mapea estos +# Página 11 → bloque 3 ($6000-$7FFF) │ +# Página 12 → bloque 4 ($8000-$9FFF) │ (datos, no ejecutable sin MC45) +# Página 13 → bloque 5 ($A000-$BFFF) ┘ + +_PAGE_MAP = [ + (1, 9), # bloque 1 → página 9 + (2, 10), # bloque 2 → página 10 + (3, 11), # bloque 3 → página 11 + (4, 12), # bloque 4 → página 12 + (5, 13), # bloque 5 → página 13 +] + +_SD81_PAGE_PORT = 0xE7 # puerto mapeador de memoria (modo full: OUT (C), A) +_STACK_TOP = 0x7FFF # pila al tope de la zona ejecutable + + +def _map_block(block: int, page: int) -> list[str]: + """Emite OUT (C), A para mapear una página a un bloque (modo full, 64 pág.).""" + return [ + f"ld b, {page}", + f"ld a, {block}", + f"ld c, {_SD81_PAGE_PORT:#04x}", + "out (c), a", + ] + + +class Backend(Z80Backend): + def init(self): + super().init() + + OPTIONS(Action.ADD_IF_NOT_DEFINED, name="org", type=int, default=_ORG) + OPTIONS(Action.ADD_IF_NOT_DEFINED, name="heap_size", type=int, + default=_HEAP_SIZE, ignore_none=True) + OPTIONS(Action.ADD_IF_NOT_DEFINED, name="heap_address", type=int, + default=_HEAP_ADDR, ignore_none=False) + OPTIONS(Action.ADD_IF_NOT_DEFINED, name="heap_start_label", type=str, + default=f"{NAMESPACE}.ZXBASIC_MEM_HEAP") + OPTIONS(Action.ADD_IF_NOT_DEFINED, name="heap_size_label", type=str, + default=f"{NAMESPACE}.ZXBASIC_HEAP_SIZE") + OPTIONS(Action.ADD_IF_NOT_DEFINED, name="headerless", type=bool, + default=False, ignore_none=True) + + self._QUAD_TABLE.update( + { + ICInstruction.END: ICInfo(1, _end), + } + ) + + engine.main() + + @staticmethod + def emit_prologue() -> list[str]: + """ + Prólogo del programa para ZX81 + SD81 Booster. + + Estructura del binario generado (ORG $0000): + $0000-$00FF vectors.asm (vectores RST, incluido desde sysvars.asm) + $0100 START_LABEL (entrada del stage 2 bootstrap) + - mapeo de bloques 1-5 a sus páginas definitivas + - SP = $7FFF + - CALL (SD81_INIT_SYSVARS, etc.) + - JP __MAIN_LABEL__ + + El stage 1 bootstrap (externo, en el cargador BASIC a $6000) ya ha: + - Configurado HFILE=$C000 y activado modo Spectrum (POKE 2045, 172) + - Desactivado el IO mapeado en memoria (POKE 2056) + - Desactivado interrupciones (DI) + - Mapeado bloque 0 → página 8 (JP $0100 ya ejecuta en RAM limpia) + """ + # -- Definiciones del heap ------------------------------------------ + heap_init = [f"{common.DATA_LABEL}:"] + + if common.REQUIRES.intersection(common.MEMINITS) or f"{NAMESPACE}.__MEM_INIT" in common.INITS: + heap_init.append( + "; Defines HEAP SIZE\n" + + OPTIONS.heap_size_label + " EQU " + str(OPTIONS.heap_size) + ) + if OPTIONS.heap_address is None: + heap_init.append(OPTIONS.heap_start_label + ":") + heap_init.append(f"DEFS {OPTIONS.heap_size}") + else: + heap_init.append( + "; Defines HEAP ADDRESS\n" + + OPTIONS.heap_start_label + f" EQU {OPTIONS.heap_address}" + ) + + heap_init.append( + "; Defines USER DATA Length in bytes\n" + + f"{NAMESPACE}.ZXBASIC_USER_DATA_LEN" + + f" EQU {common.DATA_END_LABEL} - {common.DATA_LABEL}" + ) + heap_init.append( + f"{NAMESPACE}.__LABEL__.ZXBASIC_USER_DATA_LEN" + + f" EQU {NAMESPACE}.ZXBASIC_USER_DATA_LEN" + ) + heap_init.append( + f"{NAMESPACE}.__LABEL__.ZXBASIC_USER_DATA EQU {common.DATA_LABEL}" + ) + + # -- Tabla de vectores RST ($0000-$00FF) ---------------------------- + # Debe ser lo primero en el binario. Cada RST ocupa 8 bytes. + # Usamos org absoluto para cada entrada: el ensamblador rellena los + # huecos con ceros, lo que es correcto para una zona no ejecutable. + output = ["org $0000"] + output.append("jp $0100") # $0000: reset / RST 0 → stage 2 + output.append("org $0008") + output.append("di") # $0008: RST $08 (error handler Spectrum) + output.append("halt") + output.append("org $0010") + output.append("di") # $0010-$0037: RSTs no usados + output.append("halt") + output.append("org $0018") + output.append("di") + output.append("halt") + output.append("org $0020") + output.append("di") + output.append("halt") + output.append("org $0028") + output.append("di") # $0028: RST $28 FP calc — nunca con __ZXB_NO_FLOAT + output.append("halt") + output.append("org $0030") + output.append("di") + output.append("halt") + output.append("org $0038") + output.append("di") # $0038: RST $38 IM1 — DI permanente, nunca llega + output.append("halt") + output.append("org $0066") + output.append("retn") # $0066: NMI desactivada, pero el vector debe existir + + # -- Stage 2 bootstrap en $0100 ------------------------------------ + output.append(f"org {_STAGE2_ENTRY}") + output.append(f"{common.START_LABEL}:") + + if OPTIONS.headerless: + output.extend(heap_init) + return output + + # Mapeo de bloques 1-5 a sus páginas definitivas. + # El bloque 0 ya fue mapeado a la página 8 por el stage 1 externo. + for block, page in _PAGE_MAP: + output.extend(_map_block(block, page)) + + # Pila al tope de la zona ejecutable + output.append(f"ld sp, {_STACK_TOP:#06x}") + + # Llamadas a rutinas de inicialización registradas con #init + # (SD81_INIT_SYSVARS y cualquier otra del runtime incluido) + output.extend(f"call {label}" for label in sorted(common.INITS)) + + # Salto al programa del usuario + output.append(f"jp {common.MAIN_LABEL}") + + output.extend(heap_init) + return output + + @staticmethod + def emit_epilogue() -> list[str]: + output = list(common.AT_END) + if OPTIONS.autorun: + output.append(f"END {common.START_LABEL}") + else: + output.append("END") + return output diff --git a/src/lib/arch/zx81sd/runtime/arch_config.asm b/src/lib/arch/zx81sd/runtime/arch_config.asm new file mode 100644 index 000000000..21a847240 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/arch_config.asm @@ -0,0 +1,13 @@ +; Configuración de arquitectura ZX81 + SD81 Booster +; Este fichero se incluye antes que cualquier otro del runtime. +; +; Activa la implementación de scroll por software (sin ROM Spectrum) +; y desactiva las funcionalidades que dependen de la ROM Spectrum. + +; Usar scroll por buffer propio (en vez de CALL $0DFEh ROM Spectrum) +#define __ZXB_ENABLE_BUFFER_SCROLL + +; Sin soporte de coma flotante en esta versión +; (el FP calculator de la ROM Spectrum no está disponible) +; Comentar esta línea cuando se integre fp_calc.asm +#define __ZXB_NO_FLOAT diff --git a/src/lib/arch/zx81sd/runtime/bootstrap.asm b/src/lib/arch/zx81sd/runtime/bootstrap.asm new file mode 100644 index 000000000..5b629d041 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/bootstrap.asm @@ -0,0 +1,80 @@ +; BOOTSTRAP — Inicialización de las sysvars del runtime (stage 2, parte ASM) +; +; El hardware (paginación bloques 1-5, SP, DI) es inicializado directamente +; por el prólogo del backend Python (emit_prologue), que emite esas +; instrucciones como constantes de arquitectura conocidas en tiempo de +; compilación. +; +; Esta rutina se ocupa de los valores por defecto de las sysvars en $8000+. +; Se registra con #init para que el compilador inserte automáticamente +; CALL SD81_INIT_SYSVARS en el prólogo, justo antes del salto al programa. + +#include once +#include once + +#init .core.SD81_INIT_SYSVARS + + push namespace core + +; SD81_INIT_SYSVARS — Inicializa el bloque de variables del runtime en $8000 +SD81_INIT_SYSVARS: + PROC + + ; CHARS apunta 256 bytes ANTES del inicio del font (convención Spectrum): + ; el runtime calcula glifo = CHARS + código*8, sin restar 32. + ; Así CHR$(32)=space → CHARS+256 = font[0], CHR$(72)='H' → CHARS+576 = font[40]. + ld hl, __ZX81SD_CHARSET - 256 + ld (CHARS), hl + + ; UDG: primer carácter definible por el usuario (CHR$(144) en Spectrum) + ; = font base + (144-32)*8 = font + 896. Con la convención CHARS-256: + ; UDG = CHARS + 144*8 = __ZX81SD_CHARSET - 256 + 1152 = __ZX81SD_CHARSET + 896 + ld hl, __ZX81SD_CHARSET + 896 + ld (UDG), hl + + ; Cursor al inicio de pantalla (columna=SCR_COLS, fila=SCR_ROWS) + ld a, SCR_ROWS + ld h, a + ld a, SCR_COLS + ld l, a + ld (S_POSN), hl + + ; SCREEN_ADDR / SCREEN_ATTR_ADDR son variables RAM (no constantes EQU): + ; el runtime las lee con LD HL,(SCREEN_ADDR) para obtener la dirección. + ld hl, $C000 + ld (SCREEN_ADDR), hl ; $801D ← $C000 + ld (DFCC), hl ; cursor bitmap al inicio de pantalla + + ld hl, $D800 + ld (SCREEN_ATTR_ADDR), hl ; $801F ← $D800 + ld (DFCCL), hl ; cursor attrs al inicio de atributos + + ; COORDS: último punto PLOT = (0,0) + xor a + ld (COORDS), a + ld (COORDS + 1), a + + ; Atributos por defecto: tinta negra sobre fondo blanco (INK 0, PAPER 7) + ; $38 = 0b00111000 = PAPER 7 + INK 0, igual que el defecto del Spectrum + ld a, $38 + ld (ATTR_P), a + ld hl, $F838 ; ATTR_T=$38, MASK_T=$F8 + ld (ATTR_T), hl + + ; Flags a cero + xor a + ld (FLAGS2), a + ld (P_FLAG), a + ld (TV_FLAG), a + ld (ERR_NR), a + + ; Contadores a cero + ld hl, 0 + ld (FRAMES), hl + ld (RANDOM_SEED_LOW), hl + + ret + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/border.asm b/src/lib/arch/zx81sd/runtime/border.asm new file mode 100644 index 000000000..464812eda --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/border.asm @@ -0,0 +1,16 @@ +; BORDER — Cambia el color del borde +; SD81 Booster en modo Superfast HiRes Spectrum emula el puerto ULA del +; Spectrum en $FBh: bits 2-0 = color de borde, bits 4-3 = beeper. +; +; Entrada: A = color de borde (bits 2-0, igual que en Spectrum) +; Sustituye a: BORDER EQU $229Bh (ROM Spectrum) + + push namespace core + +SD81_ULA_PORT EQU $FB ; Puerto ULA emulado en modo HiRes Spectrum + +BORDER: + out (SD81_ULA_PORT), a + ret + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/break.asm b/src/lib/arch/zx81sd/runtime/break.asm new file mode 100644 index 000000000..73f0addc9 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/break.asm @@ -0,0 +1,62 @@ +; CHECK_BREAK — Detección de la tecla BREAK (SHIFT + SPACE) +; ZX81 + SD81 Booster +; +; Los puertos del teclado del ZX81 son idénticos a los del Spectrum +; (puerto $FEh con líneas de dirección en el bus A8-A15). +; TS_BRK ($8020h de la ROM Spectrum) lee las half-rows de CAPS SHIFT y SPACE; +; aquí se reimplementa directamente sin depender de la ROM. +; +; Puerto $FEh: +; A8 (línea 0, $FEFE) = SHIFT/Z/X/C/V +; A12 (línea 7, $7FFE) = SPACE/./M/N/B ← SPACE está aquí, bit 0 +; +; BREAK = CAPS SHIFT (bit 0 de $FEFE) + SPACE (bit 0 de $7FFE) + +#include once +#include once + + push namespace core + +CHECK_BREAK: + PROC + LOCAL TS_BRK, NO_BREAK + + push af + call TS_BRK + jr c, NO_BREAK + + ld a, ERROR_BreakIntoProgram + jp __ERROR + +NO_BREAK: + pop af + pop hl ; ret address + ex (sp), hl ; restaura HL original + ret + +; TS_BRK — Comprueba si BREAK está pulsado +; Salida: carry set = no pulsado; carry clear = BREAK pulsado +; Destruye: A +TS_BRK: + ld a, $FE + in a, ($FE) ; half-row SPACE/./M/N/B (A12 high = $7FFE... ) + ; Para leer la half-row de SPACE necesitamos A12=0, resto altos: + ; Dirección: $7FFE = 0111 1111 1111 1110b → A12=0, A8=1 + ld a, $7F + in a, ($FE) ; lee half-row 7 (SPACE en bit 0) + rra ; bit 0 → carry (0 = pulsado) + jr c, NO_SPACE + + ; SPACE pulsado — comprobar CAPS SHIFT + ld a, $FE ; A8=0 → half-row 0 (CAPS SHIFT en bit 0) + in a, ($FE) + rra ; bit 0 → carry (0 = pulsado) + ret ; carry clear = ambas pulsadas = BREAK + +NO_SPACE: + scf ; SPACE no pulsada → no es BREAK + ret + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/charset.asm b/src/lib/arch/zx81sd/runtime/charset.asm new file mode 100644 index 000000000..20a0cad25 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/charset.asm @@ -0,0 +1,11 @@ +; CHARSET — Fuente de caracteres 8x8 compatible Spectrum +; +; 96 caracteres × 8 bytes, desde CHR$(32) hasta CHR$(127). +; Fichero externo: specfont.bin (debe estar en el mismo directorio). + + push namespace core + +__ZX81SD_CHARSET: + INCBIN "specfont.bin" + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/error.asm b/src/lib/arch/zx81sd/runtime/error.asm new file mode 100644 index 000000000..d0dd52c0a --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/error.asm @@ -0,0 +1,40 @@ +; Simple error control routines — ZX81 + SD81 Booster +; Sustituye RST 8 (error handler de la ROM Spectrum) por una parada +; limpia: guarda el código de error en ERR_NR y detiene la CPU. +; Las interrupciones ya están desactivadas (DI desde el bootstrap). + +#include once + + push namespace core + +; Códigos de error (compatibles con el manual del ZX Spectrum) +ERROR_Ok EQU -1 +ERROR_SubscriptWrong EQU 2 +ERROR_OutOfMemory EQU 3 +ERROR_OutOfScreen EQU 4 +ERROR_NumberTooBig EQU 5 +ERROR_InvalidArg EQU 9 +ERROR_IntOutOfRange EQU 10 +ERROR_NonsenseInBasic EQU 11 +ERROR_InvalidFileName EQU 14 +ERROR_InvalidColour EQU 19 +ERROR_BreakIntoProgram EQU 20 +ERROR_TapeLoadingErr EQU 26 + +; __ERROR — Detiene la ejecución con un código de error en A. +; Sustituye a: RST 8 (ROM Spectrum) +__ERROR: + ld (__ERROR_CODE), a + ld (ERR_NR), a ; guardar en sysvar + di ; asegurar interrupciones desactivadas + halt ; detener CPU +__ERROR_CODE: + nop ; byte de código de error (para compatibilidad con llamadores) + ret ; no se alcanza, pero mantiene la estructura original + +; __STOP — Guarda el código de error y continúa (para END del programa). +__STOP: + ld (ERR_NR), a + ret + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/fp_calc.asm b/src/lib/arch/zx81sd/runtime/fp_calc.asm new file mode 100644 index 000000000..4bd801895 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/fp_calc.asm @@ -0,0 +1,34 @@ +; FP_CALC — Calculador de coma flotante +; Sustituye a RST $28h (ROM Spectrum, dirección $0E9Bh) +; +; ESTADO: STUB — Primera versión sin soporte float. +; La arquitectura zx81sd compila con __ZXB_NO_FLOAT activado (arch_config.asm), +; por lo que este fichero no se incluye en compilaciones normales. +; +; TODO: Integrar el calculador FP del Spectrum reubicado en $0000-$0FFF +; o una reimplementación libre compatible con el protocolo de bytecodes. +; Cuando esté listo: +; 1. Eliminar #define __ZXB_NO_FLOAT de arch_config.asm +; 2. Sustituir FP_CALC_ENTRY por la implementación real +; 3. Verificar que todos los RST $28h del runtime se convierten +; en CALL FP_CALC_ENTRY (gestionado por el backend) +; +; Protocolo RST $28h del Spectrum: +; - Los bytecodes de operación siguen inmediatamente al CALL en memoria +; - El calculador lee el flujo de bytecodes desde (SP) tras el CALL +; - El stack de coma flotante (5 bytes/número) es independiente del stack Z80 +; - El registro E apunta al primer bytecode +; +; Referencias: +; ROM Spectrum disassembly: https://skoolkid.github.io/rom/ +; FP calculator entry: $0E9Bh — routine CALCULATE + + push namespace core + +; Punto de entrada del calculador FP (para cuando se integre la versión real) +FP_CALC_ENTRY: + ; STUB: no hacer nada — el compilador no debería llegar aquí + ; con __ZXB_NO_FLOAT activo + ret + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/paging.asm b/src/lib/arch/zx81sd/runtime/paging.asm new file mode 100644 index 000000000..5433ff188 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/paging.asm @@ -0,0 +1,54 @@ +; PAGING — Rutinas de paginación de memoria del SD81 Booster +; Puerto $E7h: mapeador de memoria en bloques de 8KB +; +; Modo simple (hasta 256KB, 32 páginas): +; OUT ($E7h), A con A = (página << 3) | bloque +; +; Modo completo (hasta 512KB, 64 páginas): +; OUT (C), A con A = bloque (0-7), B = página (0-63), C = $E7h +; +; Los bloques 0-3 ($0000-$7FFF) son ejecutables. +; Los bloques 4-7 ($8000-$FFFF) son solo datos (sin MC45). +; Los bloques 6-7 deben ser consistentes con el HFILE en modo normal del ZX81; +; en modo Superfast HiRes Spectrum la FPGA ignora esa restricción. + + push namespace core + +SD81_PAGE_PORT EQU $E7 ; Puerto del mapeador de memoria + +; PAGE_SET_SIMPLE — Asigna página a bloque (modo simple, hasta 32 páginas) +; Entrada: A = bloque (0-7), B = página (0-31) +; Destruye: A, C +PAGE_SET_SIMPLE: + ld c, a ; C = bloque + ld a, b + sla a + sla a + sla a ; A = página << 3 + or c ; A = (página << 3) | bloque + out (SD81_PAGE_PORT), a + ret + +; PAGE_SET_FULL — Asigna página a bloque (modo completo, hasta 64 páginas) +; Entrada: A = bloque (0-7), B = página (0-63) +; Destruye: C +PAGE_SET_FULL: + ld c, SD81_PAGE_PORT + out (c), a ; A = bloque, B = página + ret + +; PAGE_SET_SCREEN — Mapea la página de pantalla al bloque 6 ($C000) +; Entrada: B = número de página RAM dedicada a la pantalla +; Destruye: A, C +PAGE_SET_SCREEN: + ld a, 6 ; bloque 6 = $C000-$DFFF + jp PAGE_SET_FULL + +; PAGE_SET_DATA — Mapea una página de datos al bloque 7 ($E000) +; Entrada: B = número de página +; Destruye: A, C +PAGE_SET_DATA: + ld a, 7 ; bloque 7 = $E000-$FFFF + jp PAGE_SET_FULL + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/pause.asm b/src/lib/arch/zx81sd/runtime/pause.asm new file mode 100644 index 000000000..610268c32 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/pause.asm @@ -0,0 +1,29 @@ +; ----------------------------------------------------------------------- +; PAUSE — Espera N frames usando VSYNC del SD81 Booster +; Sustituye a CALL $1F3Dh (PAUSE_1 de la ROM Spectrum) que usa HALT. +; +; Entrada: HL = número de frames a esperar +; ----------------------------------------------------------------------- + +#include once + + push namespace core + +__PAUSE: + PROC + LOCAL PAUSE_LOOP + + ld b, h + ld c, l ; BC = contador de frames + +PAUSE_LOOP: + ld a, b + or c + ret z ; BC=0 → fin + call VSYNC_TICK ; espera un frame e incrementa FRAMES + dec bc + jr PAUSE_LOOP + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/pixel_addr.asm b/src/lib/arch/zx81sd/runtime/pixel_addr.asm new file mode 100644 index 000000000..1e3c065c4 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/pixel_addr.asm @@ -0,0 +1,71 @@ +; PIXEL_ADDR — Calcula la dirección del byte de pantalla para coordenadas (Y, X) +; Sustituye a PIXEL_ADDR EQU $22ACh (ROM Spectrum) +; +; Convención (igual que la ROM Spectrum, para no modificar plot.asm): +; Entrada: A = 191 - Y_real, C = X (0-255) +; Salida: HL = offset dentro del bitmap (sin la base de pantalla) +; A = máscara de bit ($80 >> (X AND 7)) +; Destruye: B, D +; +; Organización bitmap Spectrum: +; bits [12:11] = tercio vertical (V AND $C0) >> 6 +; bits [10: 8] = línea en tercio (V AND $07) +; bits [ 7: 5] = fila de carácter (V AND $38) >> 3 +; bits [ 4: 0] = columna de byte X >> 3 + + push namespace core + +PIXEL_ADDR: + PROC + LOCAL BIT_LOOP, DONE_BITS + + ld d, a ; D = V = 191-Y + + ; -- Byte alto del offset: tercio (bits 12-11) + línea en tercio (bits 10-8) -- + ld a, d + and $C0 ; A = tercio * 64 + srl a + srl a + srl a ; A = tercio * 8 → bits [5:3] de H + ld h, a + ld a, d + and $07 ; A = línea en tercio (0-7) → bits [2:0] de H + or h + ld h, a ; H = (tercio<<3) | línea_en_tercio + + ; -- Byte bajo del offset: fila de carácter (bits 7-5) + columna de byte (bits 4-0) -- + ld a, d + and $38 ; A = fila_de_char * 8 + rrca + rrca + rrca ; A = fila_de_char (0-7) + add a, a + add a, a + add a, a + add a, a + add a, a ; A = fila_de_char * 32 + ld b, a + ld a, c + rrca + rrca + rrca + and $1F ; A = X / 8 (columna de byte, 0-31) + add a, b + ld l, a ; L = fila_de_char*32 + col_byte + + ; -- Máscara de bit: $80 >> (X AND 7) -- + ld a, c + and $07 ; A = X AND 7 + ld b, a + ld a, $80 + or a + jr z, DONE_BITS ; si X AND 7 = 0, no rotar +BIT_LOOP: + rrca + djnz BIT_LOOP +DONE_BITS: + ret ; HL = offset en bitmap, A = máscara bit + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/po_gr_1.asm b/src/lib/arch/zx81sd/runtime/po_gr_1.asm new file mode 100644 index 000000000..6f78aaf99 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/po_gr_1.asm @@ -0,0 +1,88 @@ +; PO_GR_1 — Genera el patrón de bits para caracteres gráficos 128-143 +; Sustituye a PO_GR_1 EQU $0B38h (ROM Spectrum) +; +; Los caracteres gráficos Spectrum (CHR$ 128 a CHR$ 143) son bloques +; de pixels 2×2 en cuadrantes. El nibble bajo del código determina +; qué cuadrantes están encendidos (bit 0 = TL, 1 = TR, 2 = BL, 3 = BR). +; +; Entrada: B = código de carácter (128-143), solo se usan bits 3-0 +; Salida: MEM0 (5 bytes) = patrón de 8 bytes del carácter generado +; HL = MEM0 (para que plot lo use directamente) +; Destruye: A, B, C, D, E +; +; Patrón generado: 4 líneas top + 4 líneas bottom +; top_byte = $FF si bit 0 (TL) || $F0 si bit 1 (TR) || combinación +; bot_byte = igual pero mirando bits 2 (BL) y 3 (BR) +; +; Codificación exacta: +; bit 0 = cuadrante superior izquierdo → pixels $F0 en filas 0-3 +; bit 1 = cuadrante superior derecho → pixels $0F en filas 0-3 +; bit 2 = cuadrante inferior izquierdo → pixels $F0 en filas 4-7 +; bit 3 = cuadrante inferior derecho → pixels $0F en filas 4-7 + +#include once + + push namespace core + +PO_GR_1: + PROC + + ld a, b ; A = código gráfico (128-143) + and $0F ; quedarnos solo con los 4 bits de cuadrante + + ; Calcular byte superior (filas 0-3) + ld c, $00 + bit 0, a ; TL activo? + jr z, NO_TL + ld c, $F0 +NO_TL: + bit 1, a ; TR activo? + jr z, NO_TR + ld b, c + or $0F + ld c, a + ld a, b +NO_TR: + ; C = byte superior + + ; Calcular byte inferior (filas 4-7) + ld d, a ; guardar A (bits cuadrante) + ld e, $00 + bit 2, d ; BL activo? + jr z, NO_BL + ld e, $F0 +NO_BL: + bit 3, d ; BR activo? + jr z, NO_BR + ld a, e + or $0F + ld e, a +NO_BR: + ; E = byte inferior + + ; Rellenar MEM0 con 8 bytes: 4x C, 4x E + ld hl, MEM0 + ld a, c + ld (hl), a + inc hl + ld (hl), a + inc hl + ld (hl), a + inc hl + ld (hl), a + inc hl + ld a, e + ld (hl), a + inc hl + ld (hl), a + inc hl + ld (hl), a + inc hl + ld (hl), a + + ld hl, MEM0 ; devolver puntero al patrón + ret + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/print.asm b/src/lib/arch/zx81sd/runtime/print.asm new file mode 100644 index 000000000..97c589c7a --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/print.asm @@ -0,0 +1,586 @@ +; vim:ts=4:sw=4:et: +; PRINT command routine +; Does not print attribute. Use PRINT_STR or PRINT_NUM for that + +#include once +#include once +#include once +#include once +#include once +#include once +#include once +#include once +#include once +#include once +#include once +#include once +#include once + +; Putting a comment starting with @INIT
+; will make the compiler to add a CALL to
+; It is useful for initialization routines. +#init .core.__PRINT_INIT + + push namespace core + +__PRINT_INIT: ; To be called before program starts (initializes library) + PROC + + ld hl, __PRINT_START + ld (PRINT_JUMP_STATE), hl + + ;; Clears ATTR2 flags (OVER 2, etc) + xor a + ld (FLAGS2), a + ld hl, TV_FLAG + res 0, (hl) + + LOCAL SET_SCR_ADDR + call __LOAD_S_POSN + jp __SET_SCR_PTR + + ;; Receives HL = future value of S_POSN + ;; Stores it at (S_POSN) and refresh screen pointers (ATTR, SCR) +SET_SCR_ADDR: + ld (S_POSN), hl + ex de, hl + ld hl, SCR_SIZE + or a + sbc hl, de + ex de, hl + dec e + jp __SET_SCR_PTR + +__PRINTCHAR: ; Print character store in accumulator (A register) + ; Modifies H'L', B'C', A'F', D'E', A + + LOCAL PO_GR_1 + + LOCAL __PRCHAR + LOCAL __PRINT_JUMP + LOCAL __SRCADDR + LOCAL __PRINT_UDG + LOCAL __PRGRAPH + LOCAL __PRINT_START + +PRINT_JUMP_STATE EQU __PRINT_JUMP + 2 + +__PRINT_JUMP: + exx ; Switch to alternative registers + jp __PRINT_START ; Where to jump. If we print 22 (AT), next two calls jumps to AT1 and AT2 respectively + +__PRINT_START: + +__PRINT_CHR: + cp ' ' + jp c, __PRINT_SPECIAL ; Characters below ' ' are special ones (> 127 bytes away) + ex af, af' ; Saves a value (char to print) for later + + ld hl, (S_POSN) + dec l + jr nz, 1f + ld l, SCR_COLS - 1 + dec h + jr nz, 2f + +#ifndef __ZXB_DISABLE_SCROLL + inc h + push hl + call __SCROLL_SCR + pop hl +#else + ld h, SCR_ROWS - 1 +#endif +2: + call SET_SCR_ADDR + jr 4f +1: + ld (S_POSN), hl +4: + ex af, af' + + cp 80h ; Is it a "normal" (printable) char + jr c, __SRCADDR + + cp 90h ; Is it an UDG? + jr nc, __PRINT_UDG + + ; Print an 8 bit pattern (80h to 8Fh) + + ld b, a + call PO_GR_1 ; This ROM routine will generate the bit pattern at MEM0 + ld hl, MEM0 + jp __PRGRAPH + +; PO_GR_1 se incluye como rutina propia (po_gr_1.asm), no desde ROM Spectrum +#include once + +__PRINT_UDG: + sub 90h ; Sub ASC code + ld bc, (UDG) + jr __PRGRAPH0 + +__SOURCEADDR EQU (__SRCADDR + 1) ; Address of the pointer to chars source +__SRCADDR: + ld bc, (CHARS) + +__PRGRAPH0: + add a, a ; A = a * 2 (since a < 80h) ; Thanks to Metalbrain at http://foro.speccy.org + ld l, a + ld h, 0 ; HL = a * 2 (accumulator) + add hl, hl + add hl, hl ; HL = a * 8 + add hl, bc ; HL = CHARS address + +__PRGRAPH: + ex de, hl ; HL = Write Address, DE = CHARS address + +#ifndef __ZXB_DISABLE_BOLD + ld a, (FLAGS2) + bit 2, a + call nz, __BOLD +#endif + +#ifndef __ZXB_DISABLE_ITALIC + ld a, (FLAGS2) + bit 4, a + call nz, __ITALIC +#endif + + ld hl, (DFCC) + push hl + + ld b, 8 ; 8 bytes per char + +__PRCHAR: + ld a, (de) ; DE *must* be source, and HL destiny + +PRINT_MODE: ; Which operation is used to write on the screen + ; Set it with: + ; LD A, + ; LD (PRINT_MODE), A + ; + ; Available operations: + ; NORMAL : 0h --> NOP ; OVER 0 + ; XOR : AEh --> XOR (HL) ; OVER 1 + ; OR : B6h --> OR (HL) ; PUTSPRITE + ; AND : A6h --> AND (HL) ; PUTMASK + nop ; Set to one of the values above + +INVERSE_MODE: ; 00 -> NOP -> INVERSE 0 + nop ; 2F -> CPL -> INVERSE 1 + + ld (hl), a + + inc de + inc h ; Next line + djnz __PRCHAR + + pop hl + inc hl + ld (DFCC), hl + + ld hl, (DFCCL) ; current ATTR Pos + inc hl + ld (DFCCL), hl + dec hl + call __SET_ATTR + exx + ret + +; ------------- SPECIAL CHARS (< 32) ----------------- + +__PRINT_SPECIAL: ; Jumps here if it is a special char + ld hl, __PRINT_TABLE + jp JUMP_HL_PLUS_2A + +PRINT_EOL: ; Called WHENEVER there is no ";" at end of PRINT sentence + exx + +__PRINT_0Dh: ; Called WHEN printing CHR$(13) + ld hl, (S_POSN) + dec l + jr nz, 1f + dec h + jr nz, 1f +#ifndef __ZXB_DISABLE_SCROLL + inc h + push hl + call __SCROLL_SCR + pop hl +#else + ld h, SCR_ROWS - 1 +#endif +1: + ld l, 1 + +__PRINT_EOL_END: + call SET_SCR_ADDR + exx + ret + +__PRINT_COM: + exx + push hl + push de + push bc + call PRINT_COMMA + pop bc + pop de + pop hl + ret + +__PRINT_TAB: + ld hl, __PRINT_TAB1 + jr __PRINT_SET_STATE + +__PRINT_TAB1: + ld (MEM0), a + ld hl, __PRINT_TAB2 + jr __PRINT_SET_STATE + +__PRINT_TAB2: + ld a, (MEM0) ; Load tab code (ignore the current one) + ld hl, __PRINT_START + ld (PRINT_JUMP_STATE), hl + exx + push hl + push bc + push de + call PRINT_TAB + pop de + pop bc + pop hl + ret + +__PRINT_AT: + ld hl, __PRINT_AT1 + jr __PRINT_SET_STATE + +__PRINT_NOP: +__PRINT_RESTART: + ld hl, __PRINT_START + +__PRINT_SET_STATE: + ld (PRINT_JUMP_STATE), hl ; Saves next entry call + exx + ret + +__PRINT_AT1: ; Jumps here if waiting for 1st parameter + ld hl, (S_POSN) + ld h, a + ld a, SCR_ROWS + sub h + ld (S_POSN + 1), a + + ld hl, __PRINT_AT2 + jr __PRINT_SET_STATE + +__PRINT_AT2: + call __LOAD_S_POSN + ld e, a + call __SAVE_S_POSN + jp __PRINT_RESTART + +__PRINT_DEL: + call __LOAD_S_POSN ; Gets current screen position + dec e + ld a, -1 + cp e + jr nz, 3f + ld e, SCR_COLS - 2 + dec d + cp d + jr nz, 3f + ld d, SCR_ROWS - 1 +3: + call __SAVE_S_POSN + exx + ret + +__PRINT_INK: + ld hl, __PRINT_INK2 + jr __PRINT_SET_STATE + +__PRINT_INK2: + call INK_TMP + jp __PRINT_RESTART + +__PRINT_PAP: + ld hl, __PRINT_PAP2 + jr __PRINT_SET_STATE + +__PRINT_PAP2: + call PAPER_TMP + jp __PRINT_RESTART + +__PRINT_FLA: + ld hl, __PRINT_FLA2 + jr __PRINT_SET_STATE + +__PRINT_FLA2: + call FLASH_TMP + jp __PRINT_RESTART + +__PRINT_BRI: + ld hl, __PRINT_BRI2 + jr __PRINT_SET_STATE + +__PRINT_BRI2: + call BRIGHT_TMP + jp __PRINT_RESTART + +__PRINT_INV: + ld hl, __PRINT_INV2 + jr __PRINT_SET_STATE + +__PRINT_INV2: + call INVERSE_TMP + jp __PRINT_RESTART + +__PRINT_OVR: + ld hl, __PRINT_OVR2 + jr __PRINT_SET_STATE + +__PRINT_OVR2: + call OVER_TMP + jp __PRINT_RESTART + +#ifndef __ZXB_DISABLE_BOLD +__PRINT_BOLD: + ld hl, __PRINT_BOLD2 + jp __PRINT_SET_STATE + +__PRINT_BOLD2: + call BOLD_TMP + jp __PRINT_RESTART +#endif + +#ifndef __ZXB_DISABLE_ITALIC +__PRINT_ITA: + ld hl, __PRINT_ITA2 + jp __PRINT_SET_STATE + +__PRINT_ITA2: + call ITALIC_TMP + jp __PRINT_RESTART +#endif + +#ifndef __ZXB_DISABLE_BOLD + LOCAL __BOLD + +__BOLD: + push hl + ld hl, MEM0 + ld b, 8 +1: + ld a, (de) + ld c, a + rlca + or c + ld (hl), a + inc hl + inc de + djnz 1b + pop hl + ld de, MEM0 + ret +#endif + +#ifndef __ZXB_DISABLE_ITALIC + LOCAL __ITALIC + +__ITALIC: + push hl + ld hl, MEM0 + ex de, hl + ld bc, 8 + ldir + ld hl, MEM0 + srl (hl) + inc hl + srl (hl) + inc hl + srl (hl) + inc hl + inc hl + inc hl + sla (hl) + inc hl + sla (hl) + inc hl + sla (hl) + pop hl + ld de, MEM0 + ret +#endif + +#ifndef __ZXB_DISABLE_SCROLL + LOCAL __SCROLL_SCR + +# ifdef __ZXB_ENABLE_BUFFER_SCROLL +__SCROLL_SCR: ;; Scrolls screen and attrs 1 row up + ld de, (SCREEN_ADDR) + ld b, 3 +3: + push bc + ld a, 8 +1: + ld hl, 32 + add hl, de + ld bc, 32 * 7 + push de + ldir + pop de + inc d + dec a + jr nz, 1b + push hl + ld bc, -32 - 256 * 7 + add hl, bc + ex de, hl + ld a, 8 +2: + ld bc, 32 + push hl + push de + ldir + pop de + pop hl + inc d + inc h + dec a + jr nz, 2b + pop de + pop bc + djnz 3b + + dec de + ld h, d + ld l, e + ld a, 8 +3: + push hl + push de + ld (hl), b + dec de + ld bc, 31 + lddr + pop de + pop hl + dec d + dec h + dec a + jr nz, 3b + + ld de, (SCREEN_ATTR_ADDR) + ld hl, 32 + add hl, de + ld bc, 32 * 23 + ldir + + ld h, d + ld l, e + ld a, (ATTR_P) + ld (hl), a + inc de + ld bc, 31 + ldir + ret +# else +__SCROLL_SCR EQU 0DFEh ; Use ROM SCROLL +# endif +#endif + + +PRINT_COMMA: + call __LOAD_S_POSN + ld a, e + and 16 + add a, 16 + +PRINT_TAB: + ; Tabulates the number of spaces in A register + ; If the current cursor position is already A, does nothing + PROC + LOCAL LOOP + + call __LOAD_S_POSN ; e = current row + sub e + and 31 + ret z + + ld b, a +LOOP: + ld a, ' ' + call __PRINTCHAR + djnz LOOP + ret + ENDP + +PRINT_AT: ; Changes cursor to ROW, COL + ; COL in A register + ; ROW in stack + + pop hl ; Ret address + ex (sp), hl ; callee H = ROW + ld l, a + ex de, hl + + call __IN_SCREEN + ret nc ; Return if out of screen + jp __SAVE_S_POSN + + LOCAL __PRINT_COM + LOCAL __PRINT_AT1 + LOCAL __PRINT_AT2 + LOCAL __PRINT_BOLD + LOCAL __PRINT_ITA + LOCAL __PRINT_INK + LOCAL __PRINT_PAP + LOCAL __PRINT_SET_STATE + LOCAL __PRINT_TABLE + LOCAL __PRINT_TAB, __PRINT_TAB1, __PRINT_TAB2 + +#ifndef __ZXB_DISABLE_ITALIC + LOCAL __PRINT_ITA2 +#else + __PRINT_ITA EQU __PRINT_NOP +#endif + +#ifndef __ZXB_DISABLE_BOLD + LOCAL __PRINT_BOLD2 +#else + __PRINT_BOLD EQU __PRINT_NOP +#endif + +__PRINT_TABLE: ; Jump table for 0 .. 22 codes + + DW __PRINT_NOP ; 0 + DW __PRINT_NOP ; 1 + DW __PRINT_NOP ; 2 + DW __PRINT_NOP ; 3 + DW __PRINT_NOP ; 4 + DW __PRINT_NOP ; 5 + DW __PRINT_COM ; 6 COMMA + DW __PRINT_NOP ; 7 + DW __PRINT_DEL ; 8 DEL + DW __PRINT_NOP ; 9 + DW __PRINT_NOP ; 10 + DW __PRINT_NOP ; 11 + DW __PRINT_NOP ; 12 + DW __PRINT_0Dh ; 13 + DW __PRINT_BOLD ; 14 + DW __PRINT_ITA ; 15 + DW __PRINT_INK ; 16 + DW __PRINT_PAP ; 17 + DW __PRINT_FLA ; 18 + DW __PRINT_BRI ; 19 + DW __PRINT_INV ; 20 + DW __PRINT_OVR ; 21 + DW __PRINT_AT ; 22 AT + DW __PRINT_TAB ; 23 TAB + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/random.asm b/src/lib/arch/zx81sd/runtime/random.asm new file mode 100644 index 000000000..0aa7e620a --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/random.asm @@ -0,0 +1,102 @@ +; RANDOM functions — ZX81 + SD81 Booster +; Adaptación de zx48k/runtime/random.asm: +; - FRAMES usa el contador VSYNC software en SYSVAR_BASE+$19 +; - RANDOM_SEED_LOW en SYSVAR_BASE+$1B (en vez de sysvar Spectrum 23670) +; - RANDOM_SEED_HIGH sigue siendo RAND+1 (operando inline de ld de,imm16) +; - Sin cambios en el algoritmo RNG (xorshift) + +#include once + + push namespace core + +RANDOMIZE: + ; Randomize with 32 bit seed in DE HL + ; if SEED = 0, uses FRAMES counter as seed + PROC + + LOCAL TAKE_FRAMES + + ld a, h + or l + or d + or e + jr z, TAKE_FRAMES + + ld (RANDOM_SEED_LOW), hl + ld (RANDOM_SEED_HIGH), de + ret + +TAKE_FRAMES: + ; Toma la semilla del contador VSYNC (sustituye al contador de frames de la ROM) + ld hl, (FRAMES) + ld (RANDOM_SEED_LOW), hl + ld hl, 0 + ld (RANDOM_SEED_HIGH), hl + ret + + ENDP + +RANDOM_SEED_HIGH EQU RAND + 1 ; Operando inline de ld de,imm16 (self-modifying seed) + +RAND: + PROC + ld de, 0C0DEh ; yw → zt (DE = semilla alta, se sobreescribe cada vez) + ld hl, (RANDOM_SEED_LOW) ; xz → yw + ld (RANDOM_SEED_LOW), de ; x = y, z = w + ld a, e ; w = w ^ (w << 3) + add a, a + add a, a + add a, a + xor e + ld e, a + ld a, h ; t = x ^ (x << 1) + add a, a + xor h + ld d, a + rra ; t = t ^ (t >> 1) ^ w + xor d + xor e + ld d, l ; y = z + ld e, a ; w = t + ld (RANDOM_SEED_HIGH), de + ret + ENDP + +RND: + ; Returns a FLOATING point integer using RAND as mantissa + PROC + LOCAL RND_LOOP + + call RAND + ld b, h + ld c, l + + ld a, e + or d + or c + or b + ret z + + ld l, 81h + ld a, e +RND_LOOP: + dec l + sla b + rl c + rl d + rla + jp nc, RND_LOOP + + ccf + rra + rr d + rr c + rr b + + ld e, a + ld a, l + ret + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/specfont.bin b/src/lib/arch/zx81sd/runtime/specfont.bin new file mode 100644 index 0000000000000000000000000000000000000000..458e0a57ae820285073ef9a6294e81634c1b61be GIT binary patch literal 768 zcmXX^v1-FG5Iu}3qJ*F!gNFqcOr=5t8{<*xM4lGaYaB7i2fq=rw z^=^ge5FMhu8n~Y`k*2|0RXgI-?r^*$)FO0%NLr;lF)k`a-8FGPL1)UNG8oqM(;^$C(uoYxAzPl_>-#0sL&so=gdAsQ7wb@&-4W! zIcK|n<-O6s!?5PRsw(qS_Sbzu9bn_>=UJ(*m*ZiFh<=7+w`_WPJV1{*c`2ug`aj$& z{1QI*Q*E(7rf2*ByOZH?gYzrv%@Bx}u1*F0 zLP(npqO3y*Ypf4t)}<>0_q6cM%xAgoFyBy0;mWt(cff;D!(fQpZG=C7y_ZCVB4hNCdbp+^88QFJ`L{Md*TDbb R)h}%R6**l0?S8*$`U4XsW{3a) literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/runtime/sysvars.asm b/src/lib/arch/zx81sd/runtime/sysvars.asm new file mode 100644 index 000000000..df1e90b3b --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/sysvars.asm @@ -0,0 +1,57 @@ +; ----------------------------------------------------------------------- +; ZX81 + SD81 Booster System Variables +; +; SCREEN_ADDR / SCREEN_ATTR_ADDR: bloque 6 ($C000), pantalla Spectrum +; emulada por la FPGA del SD81 Booster en modo Superfast HiRes Spectrum. +; +; Las variables dinámicas del runtime se sitúan en $8000+ (bloques 4-5), +; fuera de la zona de código ejecutable ($0000-$7FFF), para no partir +; el espacio de ejecución del usuario. +; ----------------------------------------------------------------------- + +; Estos ficheros se incluyen siempre a través de sysvars.asm (primer fichero +; en incluirse en cualquier programa zx81sd) para que sus #init se registren +; en la primera pasada del preprocesador, antes de que emit_prologue() genere +; las llamadas CALL a las rutinas de inicialización. +#include once + + push namespace core + +; --- Variables dinámicas del runtime ($8000+) --------------------------- +; Zona de datos (bloques 4-5), no ejecutable sin MC45. +; +; SCREEN_ADDR y SCREEN_ATTR_ADDR son variables RAM (no constantes EQU) +; porque el runtime de zx48k las lee con direccionamiento indirecto: +; LD HL, (SCREEN_ADDR) → carga el CONTENIDO de esa posición de memoria. +; SD81_INIT_SYSVARS las inicializa con $C000 y $D800 respectivamente. + +SYSVAR_BASE EQU $8000 + +CHARS EQU SYSVAR_BASE + $00 ; DW — puntero a charset (8x8) +UDG EQU SYSVAR_BASE + $02 ; DW — puntero a UDGs +COORDS EQU SYSVAR_BASE + $04 ; DW — última coordenada PLOT (X,Y) +FLAGS2 EQU SYSVAR_BASE + $06 ; DB — flags de pantalla (OVER/INVERSE/etc.) +ECHO_E EQU SYSVAR_BASE + $07 ; DB — (reservado) +DFCC EQU SYSVAR_BASE + $08 ; DW — siguiente dirección bitmap para PRINT +DFCCL EQU SYSVAR_BASE + $0A ; DW — siguiente dirección attrs para PRINT +S_POSN EQU SYSVAR_BASE + $0C ; DW — posición cursor (H=fila, L=columna) +ATTR_P EQU SYSVAR_BASE + $0E ; DB — atributo permanente (INK/PAPER/etc.) +ATTR_T EQU SYSVAR_BASE + $0F ; DW — atributo temporal + máscara +P_FLAG EQU SYSVAR_BASE + $11 ; DB — flags de impresión (OVER/INVERSE perm.) +MEM0 EQU SYSVAR_BASE + $12 ; 5B — buffer temporal para rutinas gráficas +TV_FLAG EQU SYSVAR_BASE + $17 ; DB — flags de control de salida a pantalla +ERR_NR EQU SYSVAR_BASE + $18 ; DB — código de error (-1 = sin error) +FRAMES EQU SYSVAR_BASE + $19 ; DW — contador de frames VSYNC (software) +RANDOM_SEED_LOW EQU SYSVAR_BASE + $1B ; DW — semilla RNG (16 bits bajos) +SCREEN_ADDR EQU SYSVAR_BASE + $1D ; DW — puntero al framebuffer (init: $C000) +SCREEN_ATTR_ADDR EQU SYSVAR_BASE + $1F ; DW — puntero a atributos (init: $D800) + +; Tamaño total del bloque de sysvars: $21 bytes + +; --- Constantes de pantalla --------------------------------------------- + +SCR_COLS EQU 33 ; Columnas + 1 (32 columnas visibles) +SCR_ROWS EQU 24 ; Filas (24 filas visibles) +SCR_SIZE EQU (SCR_ROWS << 8) + SCR_COLS + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/vectors.asm b/src/lib/arch/zx81sd/runtime/vectors.asm new file mode 100644 index 000000000..64a3df145 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/vectors.asm @@ -0,0 +1,66 @@ +; VECTORS — Tabla de vectores RST del Z80 para ZX81 + SD81 Booster +; +; Ocupa $0000-$00FF (primer bloque de la zona de sistema). +; Reemplaza la ROM del ZX81 (bloque 0 remapeado a página 8 por el stage 1). +; +; Tras el remapeo de bloque 0, el stage 1 salta a $0100 (stage 2 bootstrap). +; El vector RST 0 redirige allí también, por si algo fuerza un reset software. + + push namespace core + + org $0000 + +; $0000 — RST 0 / Reset software: salta al stage 2 + jp $0100 + + defs $0008 - $, $00 + +; $0008 — RST $08 (handler de errores Spectrum): redirige a __ERROR + jp __ERROR + + defs $0010 - $, $00 + +; $0010 — RST $10 (PRINT A en Spectrum): no usado, cuelga de forma segura + di + halt + + defs $0018 - $, $00 + +; $0018 — RST $18: no usado + di + halt + + defs $0020 - $, $00 + +; $0020 — RST $20: no usado + di + halt + + defs $0028 - $, $00 + +; $0028 — RST $28 (FP calculator Spectrum): stub, no debe llegarse aquí +; con __ZXB_NO_FLOAT activo el compilador no genera RST $28 + di + halt + + defs $0030 - $, $00 + +; $0030 — RST $30: no usado + di + halt + + defs $0038 - $, $00 + +; $0038 — RST $38 (IM1 interrupt): DI permanente, nunca debería llegar + di + halt + + defs $0066 - $, $00 + +; $0066 — NMI handler: NMI desactivadas por el cargador BASIC (modo FAST), +; pero el vector debe existir por si acaso + retn + + defs $0100 - $, $00 ; relleno hasta inicio del stage 2 en $0100 + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/vsync.asm b/src/lib/arch/zx81sd/runtime/vsync.asm new file mode 100644 index 000000000..424282b4a --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/vsync.asm @@ -0,0 +1,49 @@ +; ----------------------------------------------------------------------- +; VSYNC — Sincronización con el refresco de pantalla +; SD81 Booster: puerto $A7h, bit 0 = estado VSYNC +; bit 0 = 0 → pantalla pintándose (blanking activo) +; bit 0 = 1 → blanking vertical completado, inicio de nuevo frame +; +; Sustituye a HALT + interrupción IM1 del ZX Spectrum. +; Las interrupciones están desactivadas (DI) en toda la ejecución. +; ----------------------------------------------------------------------- + +#include once + + push namespace core + +SD81_DATA_PORT EQU $A7 ; Puerto de datos MCU del SD81 Booster + +; VSYNC_WAIT — Espera al inicio del siguiente frame +; Destruye: A +; No modifica ningún otro registro. +VSYNC_WAIT: + PROC + LOCAL WAIT_LOW + LOCAL WAIT_HIGH + + ; Esperar a que VSYNC baje (por si estamos en medio de un pulso) +WAIT_LOW: + in a, (SD81_DATA_PORT) + rrca ; bit 0 → carry + jr c, WAIT_LOW ; si carry=1, todavía en blanking anterior + + ; Esperar el flanco de subida (inicio real del blanking) +WAIT_HIGH: + in a, (SD81_DATA_PORT) + rrca + jr nc, WAIT_HIGH ; si carry=0, aún no ha llegado el VSYNC + + ret + ENDP + +; VSYNC_TICK — Espera un frame e incrementa el contador FRAMES +; Destruye: A, HL +VSYNC_TICK: + call VSYNC_WAIT + ld hl, (FRAMES) + inc hl + ld (FRAMES), hl + ret + + pop namespace diff --git a/src/zxbpp/zxbpp.py b/src/zxbpp/zxbpp.py index 53c7f6449..2a81d8024 100755 --- a/src/zxbpp/zxbpp.py +++ b/src/zxbpp/zxbpp.py @@ -167,6 +167,14 @@ def set_include_path(): pwd = get_include_path(arch_) INCLUDE_MAP[arch_] = [os.path.join(pwd, "stdlib"), os.path.join(pwd, "runtime")] + # zx81sd inherits runtime files from zx48k (only overrides specific ones). + # zx81sd paths take priority: its own files shadow zx48k equivalents. + if "zx81sd" in INCLUDE_MAP: + zx48k_pwd = get_include_path("zx48k") + INCLUDE_MAP["zx81sd"].extend( + [os.path.join(zx48k_pwd, "stdlib"), os.path.join(zx48k_pwd, "runtime")] + ) + INCLUDEPATH = INCLUDE_MAP.get(config.OPTIONS.architecture, []) From 9b9861f8e41eb866dfe05dd99e45f569b0ea49c6 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Wed, 1 Jul 2026 18:19:04 +0200 Subject: [PATCH 02/27] zx81sd: fix graphics/VSYNC, add native keyboard driver and beeper - sysvars.asm/bootstrap.asm: add missing MASK_P sysvar between ATTR_P and ATTR_T so COPY_ATTR no longer corrupts INK/PAPER attributes. - pixel_addr.asm: rework to return bit position directly (matches the interface plot.asm/draw.asm expect) instead of a rotated bitmask. - vsync.asm: fix port ($AF) and semantics (bits 1-6 = pulse counter since last read, auto-resets on IN) per SD81 Booster hardware. - arch_config.asm: pull in pixel_addr.asm/po_gr_1.asm so ROM-call substitutes are available project-wide. - Add native ZX81 keyboard driver (io/keyboard/keyscan.asm) since no ROM is mapped at runtime: scans the $FEFE matrix directly and decodes to ASCII (not the ZX81 ROM's own charset/token codes, since this runtime prints via the Spectrum/ASCII charset). Wire it into INKEY$ (io/keyboard/inkey.asm) and a new stdlib/input.bas that replaces the Spectrum-ROM-dependent LAST_K/BEEPER polling. - beep.asm: key click using the SD81 Booster's emulated Spectrum ULA port ($FBh, bits 4-3), sharing a shadow byte with border.asm so border color and beeper bits don't clobber each other. Co-Authored-By: Claude --- src/lib/arch/zx81sd/runtime/arch_config.asm | 28 +- src/lib/arch/zx81sd/runtime/beep.asm | 41 +++ src/lib/arch/zx81sd/runtime/bootstrap.asm | 8 +- src/lib/arch/zx81sd/runtime/border.asm | 14 +- src/lib/arch/zx81sd/runtime/draw.asm | 335 ++++++++++++++++++ .../arch/zx81sd/runtime/io/keyboard/inkey.asm | 53 +++ .../zx81sd/runtime/io/keyboard/keyscan.asm | 175 +++++++++ src/lib/arch/zx81sd/runtime/pixel_addr.asm | 77 ++-- src/lib/arch/zx81sd/runtime/plot.asm | 90 +++++ src/lib/arch/zx81sd/runtime/sysvars.asm | 24 +- src/lib/arch/zx81sd/runtime/vsync.asm | 27 +- src/lib/arch/zx81sd/stdlib/input.bas | 137 +++++++ 12 files changed, 929 insertions(+), 80 deletions(-) create mode 100644 src/lib/arch/zx81sd/runtime/beep.asm create mode 100644 src/lib/arch/zx81sd/runtime/draw.asm create mode 100644 src/lib/arch/zx81sd/runtime/io/keyboard/inkey.asm create mode 100644 src/lib/arch/zx81sd/runtime/io/keyboard/keyscan.asm create mode 100644 src/lib/arch/zx81sd/runtime/plot.asm create mode 100644 src/lib/arch/zx81sd/stdlib/input.bas diff --git a/src/lib/arch/zx81sd/runtime/arch_config.asm b/src/lib/arch/zx81sd/runtime/arch_config.asm index 21a847240..eac1425cf 100644 --- a/src/lib/arch/zx81sd/runtime/arch_config.asm +++ b/src/lib/arch/zx81sd/runtime/arch_config.asm @@ -1,13 +1,29 @@ -; Configuración de arquitectura ZX81 + SD81 Booster -; Este fichero se incluye antes que cualquier otro del runtime. +; arch_config.asm — Constantes de arquitectura ZX81 + SD81 Booster ; -; Activa la implementación de scroll por software (sin ROM Spectrum) -; y desactiva las funcionalidades que dependen de la ROM Spectrum. +; Sobreescribe zx48k/runtime/arch_config.asm para redirigir las +; llamadas ROM del runtime hacia implementaciones propias en RAM. -; Usar scroll por buffer propio (en vez de CALL $0DFEh ROM Spectrum) +; --------------------------------------------------------------------------- +; Usar scroll por buffer propio (sin ROM Spectrum) +; --------------------------------------------------------------------------- #define __ZXB_ENABLE_BUFFER_SCROLL +; --------------------------------------------------------------------------- ; Sin soporte de coma flotante en esta versión ; (el FP calculator de la ROM Spectrum no está disponible) -; Comentar esta línea cuando se integre fp_calc.asm +; --------------------------------------------------------------------------- #define __ZXB_NO_FLOAT + +; --------------------------------------------------------------------------- +; Gráficos — implementaciones propias +; --------------------------------------------------------------------------- +; Incluir aquí los ficheros con las rutinas que sustituyen a las ROM calls. +; El resto del runtime (plot.asm, draw.asm...) incluye arch_config.asm, +; por lo que estas definiciones quedan disponibles automáticamente. + +#include once +#include once + +; Nota: SCROLL_SCR, KEY_SCAN, KEY_TEST, KEY_CODE, LD_BYTES, ROM_SAVE, etc. +; deberán implementarse cuando se necesiten. Los dejamos sin definir +; para que el enlazador falle con un error claro si se usan accidentalmente. diff --git a/src/lib/arch/zx81sd/runtime/beep.asm b/src/lib/arch/zx81sd/runtime/beep.asm new file mode 100644 index 000000000..ac6c61f3a --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/beep.asm @@ -0,0 +1,41 @@ +; BEEP — Pulso corto de beeper (clic de tecla) +; +; SD81 Booster en modo Superfast HiRes Spectrum emula el puerto ULA del +; Spectrum en $FBh: bits 2-0 = color de borde, bits 4-3 = beeper (ver +; border.asm y Apendice "Modo Spectrum" del manual del SD81 Booster). +; Ambas funciones comparten el mismo puerto de solo escritura, asi que +; se mantiene una copia sombra del ultimo byte escrito para poder +; pulsar el beeper sin alterar el color de borde actual (y viceversa). + + push namespace core + +SD81_ULA_PORT EQU $FB ; Puerto ULA emulado en modo HiRes Spectrum +SD81_ULA_BEEP_BITS EQU $18 ; bits 4-3 + +__ZX81SD_ULA_SHADOW: + DEFB 0 + +; --------------------------------------------------------------------------- +; __ZX81SD_KEYCLICK — Pulso breve de beeper (clic de tecla) +; No recibe ni devuelve nada. Registros modificados: AF, BC. +; --------------------------------------------------------------------------- +__ZX81SD_KEYCLICK: + PROC + LOCAL CLICK_DELAY + + ld hl, __ZX81SD_ULA_SHADOW + ld a, (hl) + or SD81_ULA_BEEP_BITS + out (SD81_ULA_PORT), a + + ld b, 30 +CLICK_DELAY: + djnz CLICK_DELAY + + ld a, (hl) ; restaura el byte previo (beeper apagado, borde intacto) + out (SD81_ULA_PORT), a + ret + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/bootstrap.asm b/src/lib/arch/zx81sd/runtime/bootstrap.asm index 5b629d041..a4d5452b9 100644 --- a/src/lib/arch/zx81sd/runtime/bootstrap.asm +++ b/src/lib/arch/zx81sd/runtime/bootstrap.asm @@ -42,11 +42,11 @@ SD81_INIT_SYSVARS: ; SCREEN_ADDR / SCREEN_ATTR_ADDR son variables RAM (no constantes EQU): ; el runtime las lee con LD HL,(SCREEN_ADDR) para obtener la dirección. ld hl, $C000 - ld (SCREEN_ADDR), hl ; $801D ← $C000 + ld (SCREEN_ADDR), hl ; $801E ← $C000 ld (DFCC), hl ; cursor bitmap al inicio de pantalla ld hl, $D800 - ld (SCREEN_ATTR_ADDR), hl ; $801F ← $D800 + ld (SCREEN_ATTR_ADDR), hl ; $8020 ← $D800 ld (DFCCL), hl ; cursor attrs al inicio de atributos ; COORDS: último punto PLOT = (0,0) @@ -58,7 +58,9 @@ SD81_INIT_SYSVARS: ; $38 = 0b00111000 = PAPER 7 + INK 0, igual que el defecto del Spectrum ld a, $38 ld (ATTR_P), a - ld hl, $F838 ; ATTR_T=$38, MASK_T=$F8 + xor a + ld (MASK_P), a ; $00 = sin transparencia (COPY_ATTR copia ATTR_P íntegro) + ld hl, $0038 ; ATTR_T=$38, MASK_T=$00 ld (ATTR_T), hl ; Flags a cero diff --git a/src/lib/arch/zx81sd/runtime/border.asm b/src/lib/arch/zx81sd/runtime/border.asm index 464812eda..cb7f3ce6e 100644 --- a/src/lib/arch/zx81sd/runtime/border.asm +++ b/src/lib/arch/zx81sd/runtime/border.asm @@ -1,15 +1,25 @@ ; BORDER — Cambia el color del borde ; SD81 Booster en modo Superfast HiRes Spectrum emula el puerto ULA del ; Spectrum en $FBh: bits 2-0 = color de borde, bits 4-3 = beeper. +; El beeper (ver beep.asm) usa el mismo puerto, asi que se preservan +; los bits 4-3 actuales (guardados en __ZX81SD_ULA_SHADOW) en vez de +; sobrescribir el byte completo. ; ; Entrada: A = color de borde (bits 2-0, igual que en Spectrum) ; Sustituye a: BORDER EQU $229Bh (ROM Spectrum) - push namespace core +#include once -SD81_ULA_PORT EQU $FB ; Puerto ULA emulado en modo HiRes Spectrum + push namespace core BORDER: + and $07 + ld b, a + ld hl, __ZX81SD_ULA_SHADOW + ld a, (hl) + and $18 ; conserva los bits de beeper + or b + ld (hl), a out (SD81_ULA_PORT), a ret diff --git a/src/lib/arch/zx81sd/runtime/draw.asm b/src/lib/arch/zx81sd/runtime/draw.asm new file mode 100644 index 000000000..77537ddbc --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/draw.asm @@ -0,0 +1,335 @@ +; DRAW using bresenhams algorithm and screen positioning — ZX81 + SD81 Booster +; Copyleft (k) 2010 by J. Rodriguez (a.k.a. Boriel) http://www.boriel.com +; Adaptado de zx48k/runtime/draw.asm para sustituir las llamadas ROM +; por implementaciones propias (sin ROM Spectrum disponible). +; +; Cambios respecto al original zx48k: +; - PIXEL_ADDR: llama a nuestra implementación en pixel_addr.asm +; - COORDS: usa el valor de sysvars.asm ($8000+), no $5C7D + +; Y parameter in A +; X parameter in high byte on top of the stack + +#include once +#include once + +#include once +#include once +#include once + +#include once +#include once +#include once +#include once + +;; DRAW PROCEDURE + push namespace core + + PROC + + LOCAL __DRAW1 + LOCAL __DRAW2 + LOCAL __DRAW3 + LOCAL __DRAW4, __DRAW4_LOOP + LOCAL __DRAW5 + LOCAL __DRAW6, __DRAW6_LOOP + LOCAL __DRAW_ERROR + LOCAL DX1, DX2, DY1, DY2 + LOCAL __INCX, __INCY, __DECX, __DECY + +__DRAW_ERROR: + jp __OUT_OF_SCREEN_ERR + +DRAW: + ;; ENTRY POINT + + LOCAL __DRAW_SETUP1, __DRAW_START, __PLOTOVER, __PLOTINVERSE + + ex de, hl ; DE = Y OFFSET + pop hl ; return addr + ex (sp), hl ; CALLEE => HL = X OFFSET + ld bc, (COORDS) + + ld a, c + add a, l + ld l, a + ld a, h + adc a, 0 ; HL = HL + C + ld h, a + jr nz, __DRAW_ERROR ; if a <> 0 => Out of Screen + + ld a, b + add a, e + ld e, a + ld a, d + adc a, 0 ; DE = DE + B + ld d, a + jr nz, __DRAW_ERROR ; if a <> 0 => Out of Screen + + ld a, 191 + sub e + jr c, __DRAW_ERROR ; Out of screen + + ld h, e ; now H,L = y2, x2 + +__DRAW: + ; __FASTCALL__ Entry. Plots from (COORDS) to coord H, L + push hl + ex de, hl ; D,E = y2, x2; + + ld a, (P_FLAG) + ld c, a + bit 2, a ; Test for INVERSE1 + jr z, __DRAW_SETUP1 + ld a, 2Fh ; CPL + ld (__PLOTINVERSE), a + ld a, 0A6h ; and (hl) + jp __DRAW_START + +__DRAW_SETUP1: + xor a ; nop + ld (__PLOTINVERSE), a + ld a, 0B6h ; or (hl) + bit 0, c ; Test for OVER + jr z, __DRAW_START + ld a, 0AEh ; xor (hl) + +__DRAW_START: + ld (__PLOTOVER), a ; "Pokes" last operation + exx + ld bc, (COORDS) ; B'C' = y1, x1 + ld d, b ; Saves B' in D' + ld a, 191 + call PIXEL_ADDR + res 6, h ; no-op en SD81 (H siempre $00-$17), mantiene compatibilidad + + ;; Now gets pixel mask in A register + ld b, a + inc b + xor a + scf + LOCAL __PIXEL_MASK +__PIXEL_MASK: + rra + djnz __PIXEL_MASK + + ld b, d ; Restores B' from D' + ld de, (SCREEN_ADDR) + add hl, de + pop de ; D'E' = y2, x2 + exx ; At this point: D'E' = y2,x2 coords + ; B'C' = y1, y1 coords + ; H'L' = Screen Address of pixel + + ex af, af' ; Saves A reg for later + ; A' = Pixel mask + + ld bc, (COORDS) ; B,C = y1, x1 + + ld a, e + sub c ; dx = X2 - X1 + ld c, a ; Saves dx in c + + ld a, 0Ch ; INC C opcode + ld hl, __INCX ; xi = 1 + jr nc, __DRAW1 + + ld a, c + neg ; dx = X1 - X2 + ld c, a + ld a, 0Dh ; DEC C opcode + ld hl, __DECX ; xi = -1 + +__DRAW1: + ld (DX1), a + ld (DX1 + 2), hl ; Updates DX1 call address + ld (DX2), a + ld (DX2 + 2), hl ; Updates DX2 call address + + ld a, d + sub b ; dy = Y2 - Y1 + ld b, a ; Saves dy in b + + ld a, 4 ; INC B opcode + ld hl, __INCY ; y1 = 1 + jr nc, __DRAW2 + + ld a, b + neg + ld b, a ; dy = Y2 - Y1 + ld a, 5 ; DEC B opcode + ld hl, __DECY ; y1 = -1 + +__DRAW2: + ld (DY1), a + ld (DY1 + 2), hl ; Updates DX1 call address + ld (DY2), a + ld (DY2 + 2), hl ; Updates DX2 call address + + ld a, b + sub c ; dy - dx + jr c, __DRAW_DX_GT_DY ; DX > DY + + ; At this point DY >= DX + ; -------------------------- + ; HL = error = dY / 2 + ld h, 0 + ld l, b + srl l + + ; DE = -dX + xor a + sub c + ld e, a + sbc a, a + ld d, a + + ; BC = DY + ld c, b + ld b, h + + exx + scf ; Sets Carry to signal update ATTR + ex af, af' ; Brings back pixel mask + ld e, a ; Saves it in free E register + jp __DRAW4_LOOP + +__DRAW3: ; While c != e => while y != y2 + exx + add hl, de ; error -= dX + bit 7, h ; + exx ; recover coordinates + jr z, __DRAW4 ; if error < 0 + + exx + add hl, bc ; error += dY + exx + + ld a, e +DX1: ; x += xi + inc c + call __INCX ; This address will be dynamically updated + ld e, a + +__DRAW4: + +DY1: ; y += yi + inc b + call __INCY ; This address will be dynamically updated + ld a, e ; Restores A reg. + call __FASTPLOT + +__DRAW4_LOOP: + ld a, b + cp d + jp nz, __DRAW3 + ld (COORDS), bc + ret + +__DRAW_DX_GT_DY: ; DX > DY + ; -------------------------- + ; HL = error = dX / 2 + ld h, 0 + ld l, c + srl l ; HL = error = DX / 2 + + ; DE = -dY + xor a + sub b + ld e, a + sbc a, a + ld d, a + + ; BC = dX + ld b, h + + exx + ld d, e + scf ; Sets Carry to signal update ATTR + ex af, af' ; Brings back pixel mask + ld e, a ; Saves it in free E register + jp __DRAW6_LOOP + +__DRAW5: ; While loop + exx + add hl, de ; error -= dY + bit 7, h ; if (error < 0) + exx ; Restore coords + jr z, __DRAW6 ; + exx + add hl, bc ; error += dX + exx + +DY2: ; y += yi + inc b + call __INCY ; This address will be dynamically updated + +__DRAW6: + ld a, e +DX2: ; x += xi + inc c + call __INCX ; This address will be dynamically updated + ld e, a + call __FASTPLOT + +__DRAW6_LOOP: + ld a, c ; Current X coord + cp d + jp nz, __DRAW5 + ld (COORDS), bc + ret + +__DRAW_END: + exx + ret + + ;; Given a A mask and an HL screen position + ;; return the next left position + ;; Also updates BC coords +__DECX EQU SP.PixelLeft + + ;; Like the above, but to the RIGHT + ;; Also updates BC coords +__INCX EQU SP.PixelRight + + ;; Given an HL screen position, calculates + ;; the above position + ;; Also updates BC coords +__INCY EQU SP.PixelUp + + ;; Given an HL screen position, calculates + ;; the above position + ;; Also updates BC coords +__DECY EQU SP.PixelDown + + ;; Puts the A register MASK in (HL) +__FASTPLOT: +__PLOTINVERSE: + nop ; Replace with CPL if INVERSE 1 +__PLOTOVER: + or (hl) ; Replace with XOR (hl) if OVER 1 AND INVERSE 0 + ; Replace with AND (hl) if INVERSE 1 + + ld (hl), a + ex af, af' ; Recovers flag. If Carry set => update ATTR + ld a, e ; Recovers A reg + ret nc + + push hl + push de + push bc + call SET_PIXEL_ADDR_ATTR + pop bc + pop de + pop hl + + LOCAL __FASTPLOTEND +__FASTPLOTEND: + or a ; Resets carry flag + ex af, af' ; Recovers A reg + ld a, e + ret + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/io/keyboard/inkey.asm b/src/lib/arch/zx81sd/runtime/io/keyboard/inkey.asm new file mode 100644 index 000000000..992f0c7c7 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/io/keyboard/inkey.asm @@ -0,0 +1,53 @@ +; INKEY Function (zx81sd) +; Returns a string allocated in dynamic memory +; containing the string. +; An empty string otherwise. +; +; La version de zx48k llama a rutinas de la ROM del Spectrum +; (KEY_SCAN/KEY_TEST/KEY_CODE) que no existen en tiempo de ejecucion en +; zx81sd (no hay ROM mapeada). Se sustituye por un escaneo directo de la +; matriz de teclado del ZX81 y una decodificacion propia a ASCII. + +#include once +#include once + + push namespace core + +INKEY: + PROC + LOCAL __EMPTY_INKEY + + ld bc, 3 ; 1 char length string + call __MEM_ALLOC + + ld a, h + or l + ret z ; Return if NULL (No memory) + + push hl ; Saves memory pointer + + call __ZX81SD_KEYSCAN + pop hl + or a + jr z, __EMPTY_INKEY + + ld (hl), 1 + inc hl + ld (hl), 0 + inc hl + ld (hl), a + dec hl + dec hl ; HL Points to string result + ret + +__EMPTY_INKEY: + xor a + ld (hl), a + inc hl + ld (hl), a + dec hl + ret + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/io/keyboard/keyscan.asm b/src/lib/arch/zx81sd/runtime/io/keyboard/keyscan.asm new file mode 100644 index 000000000..b51b36803 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/io/keyboard/keyscan.asm @@ -0,0 +1,175 @@ +; --------------------------------------------------------------------------- +; keyscan.asm — Escaneo directo de la matriz de teclado del ZX81 +; +; No hay ROM mapeada en tiempo de ejecucion (el bloque 0 lo ocupa por +; completo nuestro binario compilado), asi que no se puede llamar a la +; rutina KEYBOARD/DECODE de la ROM original. El hardware del teclado, en +; cambio, es el mismo ZX81 de siempre (el SD81 Booster no lo toca), asi +; que se reimplementa aqui el escaneo fisico (puerto $FEFE, 8 filas +; seleccionadas rotando el registro alto del puerto) y una decodificacion +; propia a ASCII. +; +; IMPORTANTE: la ROM del ZX81 decodifica las teclas a SU PROPIO charset +; (con codigos de token para palabras clave de BASIC), no a ASCII. El +; runtime de zx81sd usa el charset Spectrum/ASCII para imprimir (ver +; charset.asm, specfont.bin), asi que la tabla de decodificacion de abajo +; traduce directamente cada tecla a su codigo ASCII, ignorando las teclas +; que en el ZX81 producen tokens de BASIC sin equivalente ASCII simple +; (STOP, AND, OR, THEN, TO, STEP, <=, >=, <>, **, EDIT, GRAPHICS, +; FUNCTION, cursores, LPRINT, LLIST, SLOW, FAST). La tecla RUBOUT +; (SHIFT+0) se traduce a ASCII 12, y ENTER (NEWLINE) a ASCII 13, para +; mantener compatibilidad con la convencion ya usada por stdlib/input.bas. +; --------------------------------------------------------------------------- + + push namespace core + +; --------------------------------------------------------------------------- +; __ZX81SD_KEYSCAN +; +; Escanea las 8 filas de la matriz y decodifica la primera tecla +; encontrada (si hay varias pulsadas a la vez, solo se detecta una). +; +; Devuelve: +; A = codigo ASCII de la tecla pulsada, o 0 si no hay ninguna pulsada, +; o si la tecla no tiene equivalente ASCII (ver nota arriba), o si +; solo esta pulsada la tecla SHIFT sola. +; Flag Z activo si A = 0 +; +; Registros modificados: AF, BC, DE, HL +; --------------------------------------------------------------------------- +__ZX81SD_KEYSCAN: + PROC + LOCAL ROW_LOOP + LOCAL NEXT_ROW + LOCAL FIND_COL + LOCAL GOT_COL0 + LOCAL GOT_COL1 + LOCAL GOT_COL2 + LOCAL GOT_COL3 + LOCAL GOT_COL4 + LOCAL GOT_KEY + LOCAL LOOKUP + LOCAL NO_KEY + LOCAL UNSHIFT_TABLE + LOCAL SHIFT_TABLE + + ; D = indice de fila actual (0-7), E = flag SHIFT pulsado (0/1) + ld d, 0 + ld e, 0 + ld b, $FE ; B = mitad alta del puerto ($FEFE, $FDFE, ... $7FFE) + +ROW_LOOP: + ld c, $FE + in a, (c) ; lee la fila; bits 0-4 = columnas (0 = pulsada) + and $1F + cp $1F + jr z, NEXT_ROW ; ninguna tecla pulsada en esta fila + + ld l, a ; L = mapa de bits de columnas pulsadas + + ld a, d + or a + jr nz, FIND_COL + + ; Fila 0: la columna 0 es la tecla SHIFT (no genera caracter propio) + bit 0, l + jr nz, FIND_COL ; SHIFT no pulsado, comprobar columnas normalmente + ld e, 1 ; SHIFT pulsado + set 0, l ; descartar ese bit para no confundirlo con datos + ld a, l + cp $1F + jr z, NEXT_ROW ; en esta fila solo estaba pulsado SHIFT + +FIND_COL: + ; Bucle desenrollado a proposito: usar CP para comparar el contador + ; de columna sobreescribia A (el mapa de bits que se iba rotando) + ; antes de que la siguiente vuelta pudiera comprobarlo, con lo que + ; solo la columna 0 se detectaba bien. Sin bucle no hay ese riesgo. + ld a, l + rrca + jr nc, GOT_COL0 + rrca + jr nc, GOT_COL1 + rrca + jr nc, GOT_COL2 + rrca + jr nc, GOT_COL3 + rrca + jr nc, GOT_COL4 + jr NEXT_ROW ; no deberia ocurrir (ya se comprobo cp $1F antes) + +GOT_COL0: + ld c, 0 + jr GOT_KEY +GOT_COL1: + ld c, 1 + jr GOT_KEY +GOT_COL2: + ld c, 2 + jr GOT_KEY +GOT_COL3: + ld c, 3 + jr GOT_KEY +GOT_COL4: + ld c, 4 + +GOT_KEY: + ; indice de tabla = fila*5 + columna - 1 + ; (la fila 0 solo aporta 4 teclas: columnas 1-4, de ahi el -1) + ld a, d + add a, a + add a, a + add a, d ; A = fila*5 + add a, c ; A = fila*5 + columna + dec a ; A = indice (0-38) + + ld hl, UNSHIFT_TABLE + bit 0, e + jr z, LOOKUP + ld hl, SHIFT_TABLE + +LOOKUP: + ld c, a + ld b, 0 + add hl, bc + ld a, (hl) + or a ; fija flag Z segun corresponda + ret + +NEXT_ROW: + rlc b + inc d + ld a, d + cp 8 + jr nz, ROW_LOOP + +NO_KEY: + xor a + ret + + ; Orden de filas/columnas identico al de las tablas K-UNSHIFT/K-SHIFT + ; de la ROM original del ZX81 (fila 0: SHIFT,Z,X,C,V ... fila 7: + ; ENTER,L,K,J,H / SPACE,.,M,N,B), verificado contra el disassembly. +UNSHIFT_TABLE: + DEFB 'Z', 'X', 'C', 'V' + DEFB 'A', 'S', 'D', 'F', 'G' + DEFB 'Q', 'W', 'E', 'R', 'T' + DEFB '1', '2', '3', '4', '5' + DEFB '0', '9', '8', '7', '6' + DEFB 'P', 'O', 'I', 'U', 'Y' + DEFB 13, 'L', 'K', 'J', 'H' ; NEWLINE -> ENTER (ASCII 13) + DEFB ' ', '.', 'M', 'N', 'B' + +SHIFT_TABLE: + DEFB ':', ';', '?', '/' + DEFB 0, 0, 0, 0, 0 ; STOP, LPRINT, SLOW, FAST, LLIST + DEFB '"', 0, 0, 0, 0 ; "" (par de comillas), OR, STEP, <=, <> + DEFB 0, 0, 0, 0, 0 ; EDIT, AND, THEN, TO, cursor-izq + DEFB 12, 0, 0, 0, 0 ; RUBOUT (DEL=12), GRAPHICS, cursor der/arr/abj + DEFB '"', ')', '(', '$', 0 ; ", ), (, $, >= + DEFB 0, '=', '+', '-', 0 ; FUNCTION, =, +, -, ** + DEFB 0, ',', '>', '<', '*' ; £, ',', >, <, * + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/pixel_addr.asm b/src/lib/arch/zx81sd/runtime/pixel_addr.asm index 1e3c065c4..524a0c129 100644 --- a/src/lib/arch/zx81sd/runtime/pixel_addr.asm +++ b/src/lib/arch/zx81sd/runtime/pixel_addr.asm @@ -1,70 +1,65 @@ -; PIXEL_ADDR — Calcula la dirección del byte de pantalla para coordenadas (Y, X) +; PIXEL_ADDR — Calcula la dirección (offset) del byte de pantalla para (B=Y, C=X) ; Sustituye a PIXEL_ADDR EQU $22ACh (ROM Spectrum) ; -; Convención (igual que la ROM Spectrum, para no modificar plot.asm): -; Entrada: A = 191 - Y_real, C = X (0-255) -; Salida: HL = offset dentro del bitmap (sin la base de pantalla) -; A = máscara de bit ($80 >> (X AND 7)) +; Interfaz idéntica a la ROM Spectrum para compatibilidad con plot.asm / draw.asm: +; Entrada: A = 191 (límite superior Y), B = Y (0=abajo, 191=arriba), C = X (0-255) +; Salida: HL = offset dentro del bitmap desde $0000 (sin base de pantalla) +; A = X AND 7 (posición del bit, 0=izquierda/bit7, 7=derecha/bit0) ; Destruye: B, D ; -; Organización bitmap Spectrum: -; bits [12:11] = tercio vertical (V AND $C0) >> 6 -; bits [10: 8] = línea en tercio (V AND $07) -; bits [ 7: 5] = fila de carácter (V AND $38) >> 3 -; bits [ 4: 0] = columna de byte X >> 3 +; El llamador (plot.asm, draw.asm) añade la base de pantalla: +; res 6, h ; no-op en nuestro caso (H siempre en $00-$17) +; add hl, (SCREEN_ADDR) +; +; Organización bitmap Spectrum interleaved: +; V = 191 - Y (Y desde abajo → V desde arriba) +; H = (V AND $C0)>>3 | (V AND $07) → tercio + línea en tercio +; L = (V AND $38)<<2 | (C>>3) → fila de char*32 + columna byte push namespace core PIXEL_ADDR: PROC - LOCAL BIT_LOOP, DONE_BITS - ld d, a ; D = V = 191-Y + sub b ; A = 191 - Y (convierte coord Spectrum a offset desde arriba) + ld d, a ; D = V = 191-Y - ; -- Byte alto del offset: tercio (bits 12-11) + línea en tercio (bits 10-8) -- - ld a, d - and $C0 ; A = tercio * 64 - srl a - srl a - srl a ; A = tercio * 8 → bits [5:3] de H + ; -- H: tercio (bits 12-11) + línea en tercio (bits 10-8) -- + and $C0 ; A = (V AND $C0) = tercio * 64 + rrca + rrca + rrca ; A = tercio * 8 ld h, a ld a, d - and $07 ; A = línea en tercio (0-7) → bits [2:0] de H + and $07 ; A = línea en tercio (0-7) or h - ld h, a ; H = (tercio<<3) | línea_en_tercio + ld h, a ; H = (tercio<<3) | línea_en_tercio - ; -- Byte bajo del offset: fila de carácter (bits 7-5) + columna de byte (bits 4-0) -- + ; -- L: fila de carácter (bits 7-5) + columna de byte (bits 4-0) -- ld a, d - and $38 ; A = fila_de_char * 8 + and $38 ; A = fila_de_char * 8 rrca rrca - rrca ; A = fila_de_char (0-7) - add a, a - add a, a - add a, a - add a, a - add a, a ; A = fila_de_char * 32 + rrca ; A = fila_de_char (0-7) + rlca + rlca + rlca + rlca + rlca ; A = fila_de_char * 32 ld b, a ld a, c rrca rrca rrca - and $1F ; A = X / 8 (columna de byte, 0-31) + and $1F ; A = X / 8 (columna de byte, 0-31) add a, b - ld l, a ; L = fila_de_char*32 + col_byte + ld l, a ; L = fila_de_char*32 + col_byte - ; -- Máscara de bit: $80 >> (X AND 7) -- + ; -- Retornar A = X AND 7 (posición del bit dentro del byte) -- ld a, c - and $07 ; A = X AND 7 - ld b, a - ld a, $80 - or a - jr z, DONE_BITS ; si X AND 7 = 0, no rotar -BIT_LOOP: - rrca - djnz BIT_LOOP -DONE_BITS: - ret ; HL = offset en bitmap, A = máscara bit + and $07 ; A = X AND 7 + + ret ENDP diff --git a/src/lib/arch/zx81sd/runtime/plot.asm b/src/lib/arch/zx81sd/runtime/plot.asm new file mode 100644 index 000000000..f32e3581c --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/plot.asm @@ -0,0 +1,90 @@ +; MIXED __FASTCALL__ / __CALLEE__ PLOT Function — ZX81 + SD81 Booster +; Adaptado de zx48k/runtime/plot.asm para sustituir las llamadas ROM +; por implementaciones propias (sin ROM Spectrum disponible). +; +; Cambios respecto al original zx48k: +; - PIXEL_ADDR: llama a nuestra implementación en pixel_addr.asm +; - COORDS: usa el valor de sysvars.asm ($8000+), no $5C7D +; - PLOT_SUB: eliminado (el código propio no lo llama) + +; Y in A (accumulator) +; X in top of the stack + +#include once +#include once +#include once +#include once +#include once + + push namespace core + +PLOT: + PROC + + LOCAL __PLOT_ERR + LOCAL __PLOT_OVER1 + + pop hl + ex (sp), hl ; Callee + + ld b, a + ld c, h + +#ifdef SCREEN_Y_OFFSET + ld a, SCREEN_Y_OFFSET + add a, b + ld b, a +#endif + +#ifdef SCREEN_X_OFFSET + ld a, SCREEN_X_OFFSET + add a, c + ld c, a +#endif + + ld a, 191 + cp b + jr c, __PLOT_ERR ; jr is faster here (#1) + +__PLOT: ; __FASTCALL__ entry (b, c) = pixel coords (y, x) + ld (COORDS), bc ; Saves current point + ld a, 191 ; Max y coord + call PIXEL_ADDR + res 6, h ; no-op en SD81 (H siempre en $00-$17), mantiene compatibilidad + ld bc, (SCREEN_ADDR) + add hl, bc ; Now current offset + + ld b, a + inc b + ld a, 0FEh +LOCAL __PLOT_LOOP +__PLOT_LOOP: + rrca + djnz __PLOT_LOOP + + ld b, a + ld a, (P_FLAG) + ld c, a + ld a, (hl) + bit 0, c ; is it OVER 1 + jr nz, __PLOT_OVER1 + and b + +__PLOT_OVER1: + bit 2, c ; is it inverse 1 + jr nz, __PLOT_END + + xor b + cpl + +LOCAL __PLOT_END +__PLOT_END: + ld (hl), a + jp SET_PIXEL_ADDR_ATTR + +__PLOT_ERR: + jp __OUT_OF_SCREEN_ERR ; Spent 3 bytes, but saves 3 T-States at (#1) + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/sysvars.asm b/src/lib/arch/zx81sd/runtime/sysvars.asm index df1e90b3b..404b35eb7 100644 --- a/src/lib/arch/zx81sd/runtime/sysvars.asm +++ b/src/lib/arch/zx81sd/runtime/sysvars.asm @@ -36,17 +36,19 @@ DFCC EQU SYSVAR_BASE + $08 ; DW — siguiente dirección bitma DFCCL EQU SYSVAR_BASE + $0A ; DW — siguiente dirección attrs para PRINT S_POSN EQU SYSVAR_BASE + $0C ; DW — posición cursor (H=fila, L=columna) ATTR_P EQU SYSVAR_BASE + $0E ; DB — atributo permanente (INK/PAPER/etc.) -ATTR_T EQU SYSVAR_BASE + $0F ; DW — atributo temporal + máscara -P_FLAG EQU SYSVAR_BASE + $11 ; DB — flags de impresión (OVER/INVERSE perm.) -MEM0 EQU SYSVAR_BASE + $12 ; 5B — buffer temporal para rutinas gráficas -TV_FLAG EQU SYSVAR_BASE + $17 ; DB — flags de control de salida a pantalla -ERR_NR EQU SYSVAR_BASE + $18 ; DB — código de error (-1 = sin error) -FRAMES EQU SYSVAR_BASE + $19 ; DW — contador de frames VSYNC (software) -RANDOM_SEED_LOW EQU SYSVAR_BASE + $1B ; DW — semilla RNG (16 bits bajos) -SCREEN_ADDR EQU SYSVAR_BASE + $1D ; DW — puntero al framebuffer (init: $C000) -SCREEN_ATTR_ADDR EQU SYSVAR_BASE + $1F ; DW — puntero a atributos (init: $D800) - -; Tamaño total del bloque de sysvars: $21 bytes +MASK_P EQU SYSVAR_BASE + $0F ; DB — máscara permanente ($00 = sin transparencia) +ATTR_T EQU SYSVAR_BASE + $10 ; DB — atributo temporal +; MASK_T se accede implícitamente como ATTR_T+1 ($8011) via LD HL,(ATTR_T) +P_FLAG EQU SYSVAR_BASE + $12 ; DB — flags de impresión (OVER/INVERSE perm.) +MEM0 EQU SYSVAR_BASE + $13 ; 5B — buffer temporal para rutinas gráficas +TV_FLAG EQU SYSVAR_BASE + $18 ; DB — flags de control de salida a pantalla +ERR_NR EQU SYSVAR_BASE + $19 ; DB — código de error (-1 = sin error) +FRAMES EQU SYSVAR_BASE + $1A ; DW — contador de frames VSYNC (software) +RANDOM_SEED_LOW EQU SYSVAR_BASE + $1C ; DW — semilla RNG (16 bits bajos) +SCREEN_ADDR EQU SYSVAR_BASE + $1E ; DW — puntero al framebuffer (init: $C000) +SCREEN_ATTR_ADDR EQU SYSVAR_BASE + $20 ; DW — puntero a atributos (init: $D800) + +; Tamaño total del bloque de sysvars: $22 bytes ; --- Constantes de pantalla --------------------------------------------- diff --git a/src/lib/arch/zx81sd/runtime/vsync.asm b/src/lib/arch/zx81sd/runtime/vsync.asm index 424282b4a..7cb85a3e9 100644 --- a/src/lib/arch/zx81sd/runtime/vsync.asm +++ b/src/lib/arch/zx81sd/runtime/vsync.asm @@ -1,8 +1,9 @@ ; ----------------------------------------------------------------------- ; VSYNC — Sincronización con el refresco de pantalla -; SD81 Booster: puerto $A7h, bit 0 = estado VSYNC -; bit 0 = 0 → pantalla pintándose (blanking activo) -; bit 0 = 1 → blanking vertical completado, inicio de nuevo frame +; SD81 Booster: puerto $AFh +; bit 0 = estado actual de VSYNC (0/1, informativo, no se usa aquí) +; bits 6-1 = contador de pulsos de VSYNC ocurridos desde la última +; lectura del puerto (se resetea con cada IN) ; ; Sustituye a HALT + interrupción IM1 del ZX Spectrum. ; Las interrupciones están desactivadas (DI) en toda la ejecución. @@ -12,27 +13,19 @@ push namespace core -SD81_DATA_PORT EQU $A7 ; Puerto de datos MCU del SD81 Booster +SD81_DATA_PORT EQU $AF ; Puerto de datos MCU del SD81 Booster -; VSYNC_WAIT — Espera al inicio del siguiente frame +; VSYNC_WAIT — Espera a que ocurra al menos un pulso de VSYNC ; Destruye: A ; No modifica ningún otro registro. VSYNC_WAIT: PROC - LOCAL WAIT_LOW - LOCAL WAIT_HIGH + LOCAL WAIT_PULSE - ; Esperar a que VSYNC baje (por si estamos en medio de un pulso) -WAIT_LOW: +WAIT_PULSE: in a, (SD81_DATA_PORT) - rrca ; bit 0 → carry - jr c, WAIT_LOW ; si carry=1, todavía en blanking anterior - - ; Esperar el flanco de subida (inicio real del blanking) -WAIT_HIGH: - in a, (SD81_DATA_PORT) - rrca - jr nc, WAIT_HIGH ; si carry=0, aún no ha llegado el VSYNC + and $7E ; bits 6-1 = contador de pulsos + jr z, WAIT_PULSE ; si 0, ningún pulso desde la última lectura ret ENDP diff --git a/src/lib/arch/zx81sd/stdlib/input.bas b/src/lib/arch/zx81sd/stdlib/input.bas new file mode 100644 index 000000000..ec8bd5c71 --- /dev/null +++ b/src/lib/arch/zx81sd/stdlib/input.bas @@ -0,0 +1,137 @@ +' ---------------------------------------------------------------- +' This file is released under the MIT License +' +' Copyleft (k) 2008 +' by Jose Rodriguez-Rosa (a.k.a. Boriel) +' +' Simple INPUT routine (not as powerful as Sinclair BASIC's). +' Usage: A$ = INPUT(MaxChars) +' +' Version zx81sd: la version de zx48k depende de la ROM del ZX +' Spectrum (system var LAST_K actualizada por interrupcion, y la +' rutina BEEPER de la ROM). En zx81sd no hay ROM mapeada en tiempo +' de ejecucion y las interrupciones estan deshabilitadas (DI), asi +' que no existe ningun mecanismo que actualice LAST_K por si solo: +' el teclado se escanea directamente bajo demanda (ver +' runtime/io/keyboard/keyscan.asm). El pitido de tecla usa el +' beeper emulado del SD81 Booster en modo Spectrum (puerto ULA +' $FBh, ver runtime/beep.asm), no la rutina BEEPER de la ROM. +' ---------------------------------------------------------------- +#ifndef __LIBRARY_INPUT__ + +REM Avoid recursive / multiple inclusion + +#define __LIBRARY_INPUT__ + +#include once +#include once + +#pragma push(case_insensitive) +#pragma case_insensitive = True + +' ------------------------------------------------------------------ +' Function 'PRIVATE' to this module. +' Escanea el teclado hasta que se pulsa una tecla, y hasta que se +' suelta antes de devolver su codigo ASCII (evita que una pulsacion +' se registre varias veces mientras dura el bucle DO/LOOP). +' ------------------------------------------------------------------ +FUNCTION FASTCALL PRIVATEInputWaitKey AS UBYTE + ASM + push namespace core + PROC + LOCAL WAIT_PRESS + LOCAL WAIT_RELEASE + +WAIT_PRESS: + call __ZX81SD_KEYSCAN + or a + jr z, WAIT_PRESS + + ld b, a + push bc ; __ZX81SD_KEYCLICK usa B como contador de retardo + call __ZX81SD_KEYCLICK + pop bc + +WAIT_RELEASE: + push bc + call __ZX81SD_KEYSCAN + pop bc + or a + jr nz, WAIT_RELEASE + + ld a, b + ENDP + pop namespace + END ASM +END FUNCTION +#require "io/keyboard/keyscan.asm" +#require "beep.asm" + + +FUNCTION input(MaxLen AS UINTEGER) AS STRING + DIM result$ AS STRING + DIM i as UINTEGER + DIM LastK as UByte + + result$ = "" + + DO + PRIVATEInputShowCursor() + + REM Espera a que se pulse una tecla y a que se suelte (debounce) + LastK = PRIVATEInputWaitKey() + + PRIVATEInputHideCursor() + + IF LastK = 12 THEN + IF LEN(result$) THEN REM "Del" key code is 12 + IF LEN(result$) = 1 THEN + LET result$ = "" + ELSE + LET result$ = result$( TO LEN(result$) - 2) + END IF + PRINT CHR$(8); + END IF + ELSEIF LastK >= CODE(" ") AND LEN(result$) < MaxLen THEN + LET result$ = result$ + CHR$(LastK) + PRINT CHR$(LastK); + END IF + + LOOP UNTIL LastK = 13 : REM "Enter" key code is 13 + + FOR i = 1 TO LEN(result$): + PRINT OVER 0; CHR$(8) + " " + chr$(8); + NEXT + + RETURN result$ + +END FUNCTION + +#pragma pop(case_insensitive) + +' ------------------------------------------------------------------ +' Function 'PRIVATE' to this module. +' Shows a flashing cursor +' ------------------------------------------------------------------ +SUB PRIVATEInputShowCursor + REM Print a Flashing cursor at current print position + DIM x, y as UBYTE + y = csrlin() + x = pos() + PRINT AT y, x; OVER 0; FLASH 1; " "; AT y, x; +END SUB + + +' ------------------------------------------------------------------ +' Function 'PRIVATE' to this module. +' Hides the flashing cursor +' ------------------------------------------------------------------ +SUB PRIVATEInputHideCursor + REM Print a Flashing cursor at current print position + DIM x, y as UBYTE + y = csrlin() + x = pos() + PRINT AT y, x; OVER 0; FLASH 0; " "; AT y, x; +END SUB + +#endif From 1872536b530fd5285f6e5d0a7ab0112c9a39b9e6 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Wed, 1 Jul 2026 18:20:13 +0200 Subject: [PATCH 03/27] Ignore zx81sd test build artifacts (compila.bat, TESTSD81*.P/.BIN) Co-Authored-By: Claude --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 8385908be..1ca58c0ee 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,8 @@ build/ venv/ .pypi-token .coverage.* + +# zx81sd test build artifacts (compila.bat + split_sd81.py output) +/compila.bat +/TESTSD81*.P +/TESTSD81*.BIN From 9c2d154bc9faa2f68e1a057055b6a0c7a0d80563 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Thu, 2 Jul 2026 12:41:28 +0200 Subject: [PATCH 04/27] zx81sd: port Spectrum ROM FP calculator (RST 28h), FLOAT support and DRAW arc mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port the Spectrum ROM floating-point calculator to the zx81sd architecture as native runtime code (no Spectrum ROM available): - fp_calc.asm: CALCULATE engine (RST 28h + literal bytecode), FP stack sysvars, arithmetic, comparison, trig/log/exp/sqrt (series-based, as in the ROM), and STK-TO-A/STK-TO-BC/CD-PRMS1 for arc drawing. - stackf.asm: native __FPSTACK_PUSH/__FPSTACK_POP over FP_STKEND. - fp_tostr.asm / printf.asm / str.asm: PRINT and STR$ of FLOAT values (sign + integer part + up to 5 decimals, trailing zeros trimmed). - val.asm: VAL for FLOAT expressions. - draw3.asm: DRAW x,y,angle (arc mode) override, replacing fixed ROM addresses with the routines ported in fp_calc.asm. Includes optional trace instrumentation behind -D DRAW3_DEBUG. - pixel_addr.asm: fix register contract — PIXEL_ADDR now preserves DE like ROM PIXEL-ADD ($22AC). It used D as scratch, which corrupted the Bresenham Y coordinate saved in D' by draw.asm, breaking every non-horizontal DRAW line (found while debugging arc mode). - sysvars.asm / bootstrap.asm: FP calculator sysvars (STKBOT/STKEND/ BREG/MEM + stack and MEM area) and their initialization. - backend main.py: include fp_calc.asm unconditionally in the prologue (RST 28h vector must always exist). Verified on hardware/EightyOne against a real Spectrum reference: PLOT 100,100: DRAW 20,0,3.14159: DRAW 10,10,1.5708: CIRCLE 60,30,20 renders identically on both. Co-Authored-By: Claude Fable 5 --- src/arch/zx81sd/backend/main.py | 3 +- src/lib/arch/zx81sd/runtime/bootstrap.asm | 15 + src/lib/arch/zx81sd/runtime/draw3.asm | 465 +++++ src/lib/arch/zx81sd/runtime/fp_calc.asm | 2016 +++++++++++++++++++- src/lib/arch/zx81sd/runtime/fp_tostr.asm | 298 +++ src/lib/arch/zx81sd/runtime/pixel_addr.asm | 18 +- src/lib/arch/zx81sd/runtime/printf.asm | 37 + src/lib/arch/zx81sd/runtime/stackf.asm | 97 + src/lib/arch/zx81sd/runtime/str.asm | 54 + src/lib/arch/zx81sd/runtime/sysvars.asm | 20 +- src/lib/arch/zx81sd/runtime/val.asm | 210 ++ 11 files changed, 3192 insertions(+), 41 deletions(-) create mode 100644 src/lib/arch/zx81sd/runtime/draw3.asm create mode 100644 src/lib/arch/zx81sd/runtime/fp_tostr.asm create mode 100644 src/lib/arch/zx81sd/runtime/printf.asm create mode 100644 src/lib/arch/zx81sd/runtime/stackf.asm create mode 100644 src/lib/arch/zx81sd/runtime/str.asm create mode 100644 src/lib/arch/zx81sd/runtime/val.asm diff --git a/src/arch/zx81sd/backend/main.py b/src/arch/zx81sd/backend/main.py index 940279407..df868e78d 100644 --- a/src/arch/zx81sd/backend/main.py +++ b/src/arch/zx81sd/backend/main.py @@ -155,8 +155,7 @@ def emit_prologue() -> list[str]: output.append("di") output.append("halt") output.append("org $0028") - output.append("di") # $0028: RST $28 FP calc — nunca con __ZXB_NO_FLOAT - output.append("halt") + output.append("jp .core.FP_CALC_ENTRY") # $0028: RST $28 -> calculador FP propio output.append("org $0030") output.append("di") output.append("halt") diff --git a/src/lib/arch/zx81sd/runtime/bootstrap.asm b/src/lib/arch/zx81sd/runtime/bootstrap.asm index a4d5452b9..e6b6b3cd7 100644 --- a/src/lib/arch/zx81sd/runtime/bootstrap.asm +++ b/src/lib/arch/zx81sd/runtime/bootstrap.asm @@ -12,6 +12,13 @@ #include once #include once +; fp_calc.asm se incluye siempre (no solo cuando el programa usa FLOAT): +; el vector RST $28h (src/arch/zx81sd/backend/main.py, emit_prologue) salta +; incondicionalmente a FP_CALC_ENTRY, así que esa etiqueta debe existir en +; todo binario, aunque el programa concreto no acabe generando código que +; la invoque. +#include once + #init .core.SD81_INIT_SYSVARS push namespace core @@ -75,6 +82,14 @@ SD81_INIT_SYSVARS: ld (FRAMES), hl ld (RANDOM_SEED_LOW), hl + ; Calculador de coma flotante (fp_calc.asm): pila FP vacía, MEM en su + ; posición por defecto (ver sysvars.asm) + ld hl, FP_CALC_STACK + ld (FP_STKBOT), hl + ld (FP_STKEND), hl + ld hl, FP_MEM_AREA + ld (FP_MEM), hl + ret ENDP diff --git a/src/lib/arch/zx81sd/runtime/draw3.asm b/src/lib/arch/zx81sd/runtime/draw3.asm new file mode 100644 index 000000000..e70bf27ef --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/draw3.asm @@ -0,0 +1,465 @@ +; draw3.asm (zx81sd) — DRAW en modo arco (DRAW x, y, angulo) +; +; Sustituye a zx48k/runtime/draw3.asm, que llama directamente a direcciones +; fijas de la ROM del Spectrum (STACK-A/STACK-BC en $2D28/$2D2Bh, STK-TO-A en +; $2314h, STK-TO-BC en $2307h, CD-PRMS1 en $247Dh) para nada de eso hay ROM +; mapeada en zx81sd. Aqui se usan las mismas rutinas, pero ya portadas como +; codigo propio en fp_calc.asm (Fases 1, 4 y 5). El resto del algoritmo (que +; es codigo bytecode del calculador: rst 28h + literales) es identico al +; original, ya que rst 28h ya apunta a nuestro CALCULATE propio. + +#include once +#include once +#include once +#include once +#include once +#include once + + push namespace core + +DRAW3: + PROC + LOCAL L2477 + LOCAL L2420 + LOCAL L2439 + LOCAL L245F + LOCAL L23C1 + LOCAL SUM_C, SUM_B + LOCAL DR_SIN_NZ + + exx + ex af, af' ;; Preserves ARC + pop hl + pop de + ex (sp), hl ;; CALLEE + push de + call __FPSTACK_I16 ;; X Offset + pop hl + call __FPSTACK_I16 ;; Y Offset + exx + ex af, af' + call __FPSTACK_PUSH ;; R Arc + +; Now enter the calculator and store the complete rotation angle in mem-5 + + RST 28H ;; FP-CALC x, y, A. + DEFB $C5 ;;st-mem-5 x, y, A. + +; Test the angle for the special case of 360 degrees. + + DEFB $A2 ;;stk-half x, y, A, 1/2. + DEFB $04 ;;multiply x, y, A/2. + DEFB $1F ;;sin x, y, sin(A/2). + DEFB $31 ;;duplicate x, y, sin(A/2),sin(A/2) + DEFB $30 ;;not x, y, sin(A/2), (0/1). + DEFB $30 ;;not x, y, sin(A/2), (1/0). + DEFB $00 ;;jump-true x, y, sin(A/2). + + DEFB (DR_SIN_NZ - $) & 0FFh + ;;if sin(r/2) is not zero. + +; The third parameter is 2*PI (or a multiple of 2*PI) so a 360 degrees turn +; would just be a straight line. Eliminating this case here prevents +; division by zero at later stage. + + DEFB $02 ;;delete x, y. + DEFB $38 ;;end-calc x, y. + JP L2477 + +; --- + +; An arc can be drawn. + +DR_SIN_NZ: + DEFB $C0 ;;st-mem-0 x, y, sin(A/2). store mem-0 + DEFB $02 ;;delete x, y. + +; The next step calculates (roughly) the diameter of the circle of which the +; arc will form part. + + DEFB $C1 ;;st-mem-1 x, y. store mem-1 + DEFB $02 ;;delete x. + + DEFB $31 ;;duplicate x, x. + DEFB $2A ;;abs x, x (+ve). + DEFB $E1 ;;get-mem-1 x, X, y. + DEFB $01 ;;exchange x, y, X. + DEFB $E1 ;;get-mem-1 x, y, X, y. + DEFB $2A ;;abs x, y, X, Y (+ve). + DEFB $0F ;;addition x, y, X+Y. + DEFB $E0 ;;get-mem-0 x, y, X+Y, sin(A/2). + DEFB $05 ;;division x, y, X+Y/sin(A/2). + DEFB $2A ;;abs x, y, X+Y/sin(A/2) = D. + + DEFB $E0 ;;get-mem-0 x, y, D, sin(A/2). + DEFB $01 ;;exchange x, y, sin(A/2), D. + + DEFB $38 ;;end-calc x, y, sin(A/2), D. + +; The next test avoids drawing 4 straight lines when the start and end pixels +; are adjacent (or the same). + + LD A,(HL) ; fetch exponent byte of D. + CP $81 ; compare to 1 + JR NC,L23C1 ; forward, if > 1, to DR-PRMS + +; else delete the top two stack values and draw a simple straight line. + + RST 28H ;; FP-CALC + DEFB $02 ;;delete + DEFB $02 ;;delete + DEFB $38 ;;end-calc x, y. + + JP L2477 ; to LINE-DRAW + +; --- + +; The ARC will consist of multiple straight lines so call CD-PRMS1 to +; pre-calculate sine values from the angle (in mem-5) and determine also +; the number of straight lines from that value and the 'diameter' which +; is at the top of the calculator stack. + +#ifdef DRAW3_DEBUG +DRAW3_BP_PRMS: ; breakpoint: justo antes de llamar a CD-PRMS1 +#endif +L23C1: CALL L247D ; routine CD-PRMS1 + + ; mem-0 ; (A)/No. of lines (=a) (step angle) + ; mem-1 ; sin(a/2) + ; mem-2 ; - + ; mem-3 ; cos(a) const + ; mem-4 ; sin(a) const + ; mem-5 ; Angle of rotation (A) in + ; B ; Count of straight lines - max 252. + + PUSH BC ; Save the line count on the machine stack. + +; Remove the now redundant diameter value D. + + RST 28H ;; FP-CALC x, y, sin(A/2), D. + DEFB $02 ;;delete x, y, sin(A/2). + +; Dividing the sine of the step angle by the sine of the total angle gives +; the length of the initial chord on a unary circle. + + DEFB $E1 ;;get-mem-1 x, y, sin(A/2), sin(a/2) + DEFB $01 ;;exchange x, y, sin(a/2), sin(A/2) + DEFB $05 ;;division x, y, sin(a/2)/sin(A/2) + DEFB $C1 ;;st-mem-1 x, y. f. + DEFB $02 ;;delete x, y. + +; With the factor stored, scale the x coordinate first. + + DEFB $01 ;;exchange y, x. + DEFB $31 ;;duplicate y, x, x. + DEFB $E1 ;;get-mem-1 y, x, x, f. + DEFB $04 ;;multiply y, x, x*f (=xx) + DEFB $C2 ;;st-mem-2 y, x, xx. + DEFB $02 ;;delete y. x. + +; Now scale the y coordinate. + + DEFB $01 ;;exchange x, y. + DEFB $31 ;;duplicate x, y, y. + DEFB $E1 ;;get-mem-1 x, y, y, f + DEFB $04 ;;multiply x, y, y*f (=yy) + +; 'sin' and 'cos' trash locations mem-0 to mem-2 so fetch mem-2 to the +; calculator stack for safe keeping. + + DEFB $E2 ;;get-mem-2 x, y, yy, xx. + +; Rotate the first arc through (A-a)/2 radians. +; +; xRotated = y * sin(angle) + x * cos(angle) +; yRotated = y * cos(angle) - x * sin(angle) +; + + DEFB $E5 ;;get-mem-5 x, y, yy, xx, A. + DEFB $E0 ;;get-mem-0 x, y, yy, xx, A, a. + DEFB $03 ;;subtract x, y, yy, xx, A-a. + DEFB $A2 ;;stk-half x, y, yy, xx, A-a, 1/2. + DEFB $04 ;;multiply x, y, yy, xx, (A-a)/2. (=angle) + DEFB $31 ;;duplicate x, y, yy, xx, angle, angle. + DEFB $1F ;;sin x, y, yy, xx, angle, sin(angle) + DEFB $C5 ;;st-mem-5 x, y, yy, xx, angle, sin(angle) + DEFB $02 ;;delete x, y, yy, xx, angle + + DEFB $20 ;;cos x, y, yy, xx, cos(angle). + +; mem-0, mem-1 and mem-2 can be used again now... + + DEFB $C0 ;;st-mem-0 x, y, yy, xx, cos(angle). + DEFB $02 ;;delete x, y, yy, xx. + + DEFB $C2 ;;st-mem-2 x, y, yy, xx. + DEFB $02 ;;delete x, y, yy. + + DEFB $C1 ;;st-mem-1 x, y, yy. + DEFB $E5 ;;get-mem-5 x, y, yy, sin(angle) + DEFB $04 ;;multiply x, y, yy*sin(angle). + DEFB $E0 ;;get-mem-0 x, y, yy*sin(angle), cos(angle) + DEFB $E2 ;;get-mem-2 x, y, yy*sin(angle), cos(angle), xx. + DEFB $04 ;;multiply x, y, yy*sin(angle), xx*cos(angle). + DEFB $0F ;;addition x, y, xRotated. + DEFB $E1 ;;get-mem-1 x, y, xRotated, yy. + DEFB $01 ;;exchange x, y, yy, xRotated. + DEFB $C1 ;;st-mem-1 x, y, yy, xRotated. + DEFB $02 ;;delete x, y, yy. + + DEFB $E0 ;;get-mem-0 x, y, yy, cos(angle). + DEFB $04 ;;multiply x, y, yy*cos(angle). + DEFB $E2 ;;get-mem-2 x, y, yy*cos(angle), xx. + DEFB $E5 ;;get-mem-5 x, y, yy*cos(angle), xx, sin(angle). + DEFB $04 ;;multiply x, y, yy*cos(angle), xx*sin(angle). + DEFB $03 ;;subtract x, y, yRotated. + DEFB $C2 ;;st-mem-2 x, y, yRotated. + +; Now the initial x and y coordinates are made positive and summed to see +; if they measure up to anything significant. + + DEFB $2A ;;abs x, y, yRotated'. + DEFB $E1 ;;get-mem-1 x, y, yRotated', xRotated. + DEFB $2A ;;abs x, y, yRotated', xRotated'. + DEFB $0F ;;addition x, y, yRotated+xRotated. + DEFB $02 ;;delete x, y. + + DEFB $38 ;;end-calc x, y. + +; Although the test value has been deleted it is still above the calculator +; stack in memory and conveniently DE which points to the first free byte +; addresses the exponent of the test value. + + LD A,(DE) ; Fetch exponent of the length indicator. + CP $81 ; Compare to that for 1 + + POP BC ; Balance the machine stack + + JP C,L2477 ; forward, if the coordinates of first line + ; don't add up to more than 1, to LINE-DRAW + +; Continue when the arc will have a discernable shape. + + PUSH BC ; Restore line counter to the machine stack. + +; The parameters of the DRAW command were relative and they are now converted +; to absolute coordinates by adding to the coordinates of the last point +; plotted. + + RST 28H ;; FP-CALC x, y. + DEFB $01 ;;exchange y, x. + DEFB $38 ;;end-calc y, x. + + LD A,(COORDS) ;; Fetch System Variable COORDS-x + CALL L2D28 ;; routine STACK-A + + RST 28H ;; FP-CALC y, x, last-x. + +; Store the last point plotted to initialize the moving ax value. + + DEFB $C0 ;;st-mem-0 y, x, last-x. + DEFB $0F ;;addition y, absolute x. + DEFB $01 ;;exchange tx, y. + DEFB $38 ;;end-calc tx, y. + + LD A,(COORDS + 1) ; Fetch System Variable COORDS-y + CALL L2D28 ; routine STACK-A + + RST 28H ;; FP-CALC tx, y, last-y. + +; Store the last point plotted to initialize the moving ay value. + + DEFB $C5 ;;st-mem-5 tx, y, last-y. + DEFB $0F ;;addition tx, ty. + +; Fetch the moving ax and ay to the calculator stack. + + DEFB $E0 ;;get-mem-0 tx, ty, ax. + DEFB $E5 ;;get-mem-5 tx, ty, ax, ay. + DEFB $38 ;;end-calc tx, ty, ax, ay. + + POP BC ; Restore the straight line count. + +; ----------------------------------- +; THE 'CIRCLE/DRAW CONVERGENCE POINT' +; ----------------------------------- + +L2420: + DEC B ; decrement the arc count (4,8,12,16...). + + JP L2439 ; forward to ARC-START + +; -------------- +; THE 'ARC LOOP' +; -------------- + +L2425: +#ifdef DRAW3_DEBUG +DRAW3_BP_ARCLOOP: ; breakpoint: inicio de cada vuelta del bucle (rotacion) +#endif + RST 28H ;; FP-CALC + DEFB $E1 ;;get-mem-1 rx. + DEFB $31 ;;duplicate rx, rx. + DEFB $E3 ;;get-mem-3 cos(a) + DEFB $04 ;;multiply rx, rx*cos(a). + DEFB $E2 ;;get-mem-2 rx, rx*cos(a), ry. + DEFB $E4 ;;get-mem-4 rx, rx*cos(a), ry, sin(a). + DEFB $04 ;;multiply rx, rx*cos(a), ry*sin(a). + DEFB $03 ;;subtract rx, rx*cos(a) - ry*sin(a) + DEFB $C1 ;;st-mem-1 rx, new relative x rotated. + DEFB $02 ;;delete rx. + + DEFB $E4 ;;get-mem-4 rx, sin(a). + DEFB $04 ;;multiply rx*sin(a) + DEFB $E2 ;;get-mem-2 rx*sin(a), ry. + DEFB $E3 ;;get-mem-3 rx*sin(a), ry, cos(a). + DEFB $04 ;;multiply rx*sin(a), ry*cos(a). + DEFB $0F ;;addition rx*sin(a) + ry*cos(a). + DEFB $C2 ;;st-mem-2 new relative y rotated. + DEFB $02 ;;delete . + DEFB $38 ;;end-calc . + +;; ARC-START +L2439: +#ifdef DRAW3_DEBUG +DRAW3_BP_ARCSTART: ; breakpoint: calculo de Dx/Dy de cada segmento +#endif + PUSH BC ; Preserve the arc counter on the machine stack. + + RST 28H ;; FP-CALC ax, ay. + DEFB $C0 ;;st-mem-0 ax, ay. + DEFB $02 ;;delete ax. + + DEFB $E1 ;;get-mem-1 ax, xr. + DEFB $0F ;;addition ax+xr (= new ax). + DEFB $31 ;;duplicate ax, ax. + DEFB $38 ;;end-calc ax, ax. + + LD A,(COORDS) ; COORDS-x last x (integer ix 0-255) + CALL L2D28 ; routine STACK-A + + RST 28H ;; FP-CALC ax, ax, ix. + DEFB $03 ;;subtract ax, ax-ix = relative DRAW Dx. + + DEFB $E0 ;;get-mem-0 ax, Dx, ay. + DEFB $E2 ;;get-mem-2 ax, Dx, ay, ry. + DEFB $0F ;;addition ax, Dx, ay+ry (= new ay). + DEFB $C0 ;;st-mem-0 ax, Dx, ay. + DEFB $01 ;;exchange ax, ay, Dx, + DEFB $E0 ;;get-mem-0 ax, ay, Dx, ay. + DEFB $38 ;;end-calc ax, ay, Dx, ay. + + LD A,(COORDS + 1) ; COORDS-y last y (integer iy 0-175) + CALL L2D28 ; routine STACK-A + + RST 28H ;; FP-CALC ax, ay, Dx, ay, iy. + DEFB $03 ;;subtract ax, ay, Dx, ay-iy ( = Dy). + DEFB $38 ;;end-calc ax, ay, Dx, Dy. + + CALL L2477 ; Routine DRAW-LINE draws (Dx,Dy) relative to + ; the last pixel plotted leaving absolute x + ; and y on the calculator stack. + ; ax, ay. + + POP BC ; Restore the arc counter from the machine stack. + + DJNZ L2425 ; Decrement and loop while > 0 to ARC-LOOP + +; ------------- +; THE 'ARC END' +; ------------- + +L245F: +#ifdef DRAW3_DEBUG +DRAW3_BP_ARCEND: ; breakpoint: ultimo segmento hasta el punto final +#endif + RST 28H ;; FP-CALC tx, ty, ax, ay. + DEFB $02 ;;delete tx, ty, ax. + DEFB $02 ;;delete tx, ty. + DEFB $01 ;;exchange ty, tx. + DEFB $38 ;;end-calc ty, tx. + +; First calculate the relative x coordinate to the end-point. + + LD A,(COORDS) ; COORDS-x + CALL L2D28 ; routine STACK-A + + RST 28H ;; FP-CALC ty, tx, coords_x. + DEFB $03 ;;subtract ty, rx. + +; Next calculate the relative y coordinate to the end-point. + + DEFB $01 ;;exchange rx, ty. + DEFB $38 ;;end-calc rx, ty. + + LD A,(COORDS + 1) ; COORDS-y + CALL L2D28 ; routine STACK-A + + RST 28H ;; FP-CALC rx, ty, coords_y + DEFB $03 ;;subtract rx, ry. + DEFB $38 ;;end-calc rx, ry. +; Finally draw the last straight line. +L2477: +#ifdef DRAW3_DEBUG +DRAW3_BP_LINE: ; breakpoint: aqui B,C,D,E = linea que se va a dibujar +#endif + call L2307 ;;Pops x, and y, and stores it in B, C + +#ifdef DRAW3_DEBUG + push af + push hl + ld a, (DRAW3_DEBUG_N) + cp 16 + jr nc, DRAW3_DEBUG_SKIP2 + ld hl, (DRAW3_DEBUG_PTR) + ld (hl), b ; Y magnitude + inc hl + ld (hl), c ; X magnitude + inc hl + ld (hl), d ; Y sign (bit7 via RL later) + inc hl + ld (hl), e ; X sign (bit7 via RL later) + inc hl + ld a, (COORDS) + ld (hl), a ; COORDS-x before this line + inc hl + ld a, (COORDS + 1) + ld (hl), a ; COORDS-y before this line + inc hl + ld (DRAW3_DEBUG_PTR), hl + ld a, (DRAW3_DEBUG_N) + inc a + ld (DRAW3_DEBUG_N), a +DRAW3_DEBUG_SKIP2: + pop hl + pop af +#endif + + ld hl, (COORDS) ;;Calculates x2 and y2 in L, H + + rl e ;; Rotate left to carry + ld a, c + jr nc, SUM_C + neg +SUM_C: + add a, l + ld l, a ;; X2 + + rl d ;; Low sign to carry + ld a, b + jr nc, SUM_B + neg +SUM_B: + add a, h + ld h, a + jp __DRAW ;;forward to LINE-DRAW (Fastcalled) + + ENDP + +#ifdef DRAW3_DEBUG +DRAW3_DEBUG_N: defb 0 +DRAW3_DEBUG_PTR: defw DRAW3_DEBUG_BUF +DRAW3_DEBUG_BUF: defs 16 * 6 +#endif + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/fp_calc.asm b/src/lib/arch/zx81sd/runtime/fp_calc.asm index 4bd801895..adc53f1c7 100644 --- a/src/lib/arch/zx81sd/runtime/fp_calc.asm +++ b/src/lib/arch/zx81sd/runtime/fp_calc.asm @@ -1,34 +1,1996 @@ -; FP_CALC — Calculador de coma flotante -; Sustituye a RST $28h (ROM Spectrum, dirección $0E9Bh) -; -; ESTADO: STUB — Primera versión sin soporte float. -; La arquitectura zx81sd compila con __ZXB_NO_FLOAT activado (arch_config.asm), -; por lo que este fichero no se incluye en compilaciones normales. -; -; TODO: Integrar el calculador FP del Spectrum reubicado en $0000-$0FFF -; o una reimplementación libre compatible con el protocolo de bytecodes. -; Cuando esté listo: -; 1. Eliminar #define __ZXB_NO_FLOAT de arch_config.asm -; 2. Sustituir FP_CALC_ENTRY por la implementación real -; 3. Verificar que todos los RST $28h del runtime se convierten -; en CALL FP_CALC_ENTRY (gestionado por el backend) -; -; Protocolo RST $28h del Spectrum: -; - Los bytecodes de operación siguen inmediatamente al CALL en memoria -; - El calculador lee el flujo de bytecodes desde (SP) tras el CALL -; - El stack de coma flotante (5 bytes/número) es independiente del stack Z80 -; - El registro E apunta al primer bytecode -; -; Referencias: -; ROM Spectrum disassembly: https://skoolkid.github.io/rom/ -; FP calculator entry: $0E9Bh — routine CALCULATE +; =========================================================================== +; fp_calc.asm — Calculador de coma flotante (RST $28h) para ZX81 + SD81 Booster +; +; Port del calculador de la ROM del ZX Spectrum 48K (CALCULATE, $335B, y las +; rutinas de las que depende) a código propio en RAM, ya que en zx81sd no hay +; ninguna ROM mapeada en tiempo de ejecucion (el bloque 0 lo ocupa entero +; nuestro binario compilado). +; +; Gracias a este port, todo el runtime de coma flotante de zx48k (addf.asm, +; subf.asm, mulf.asm, divf.asm, negf.asm, cmp/*.asm, bool/*.asm, math/*.asm, +; str.asm, printf.asm, val.asm...) funciona TAL CUAL, sin modificar ni un +; solo fichero: todos ellos hacen "rst 28h" seguido de bytes de "literal" de +; operacion calculadora, y ahora $0028 contiene codigo real (ver +; src/arch/zx81sd/backend/main.py, emit_prologue) en vez de un DI/HALT. +; +; FASE 1 (este fichero): motor CALCULATE + pila de numeros FP + aritmetica +; (suma/resta/multiplicacion/division) + comparaciones numericas + booleanas +; (AND/OR/NOT) + funciones unarias basicas (ABS/NEGATE/SGN/INT/truncate). +; Pendiente en fases posteriores: SIN/COS/TAN/ASN/ACS/ATN/LN/EXP/SQR (usan el +; "series generator" ya portado aqui, mas la tabla de coeficientes de cada +; funcion, que aun no esta), STR$ (conversion numero->texto) y VAL (parseo de +; texto->numero, con semantica simplificada: solo literales decimales, no +; evaluacion de expresiones completas como hace la ROM real — ver conversacion +; de diseño). +; +; Formato de los numeros (5 bytes), identico al de la ROM Spectrum: +; Entero pequeño: byte1=$00, byte2=signo($00/$FF), byte3=lo, byte4=hi, byte5=$00 +; Coma flotante: byte1=exponente sesgado(+$80), byte2..5=mantisa de 32 bits +; con el bit 7 del byte2 usado como signo (bit de mantisa +; implicito, siempre a 1 salvo cuando se usa para el signo) +; +; Referencia: disassembly comentado de la ROM del ZX Spectrum 48K +; (C:\ClaudeCode\ZXBASIC-SD81\Spectrum48.asm), seccion "FLOATING-POINT +; CALCULATOR". Las etiquetas Lxxxx conservan la direccion original de la ROM +; (solo como identificador legible, no como direccion real: aqui son +; reubicables) para facilitar la referencia cruzada con el disassembly. +; =========================================================================== + +#include once +#include once +#include once +; fp_calc.asm se incluye siempre (no solo cuando el programa usa FLOAT): +; ver src/arch/zx81sd/backend/main.py. Por eso debe bastarse a si mismo e +; incluir aqui stackf.asm (de donde vienen __FPSTACK_PUSH/__FPSTACK_POP), +; en vez de depender de que el fichero que lo incluya lo haga tambien. push namespace core -; Punto de entrada del calculador FP (para cuando se integre la versión real) +; --------------------------------------------------------------------------- +; Punto de entrada — sustituye a RST $28h +; --------------------------------------------------------------------------- FP_CALC_ENTRY: - ; STUB: no hacer nada — el compilador no debería llegar aquí - ; con __ZXB_NO_FLOAT activo + jp L335B + +; --------------------------------------------------------------------------- +; TEST-ROOM propio — sustituye a TEST-ROOM ($1F05, que comprobaba espacio +; libre contra el puntero de pila SP). Aqui la pila de numeros FP es un +; buffer fijo (FP_CALC_STACK..FP_CALC_STACK_END en sysvars.asm), asi que +; basta comprobar que FP_STKEND + BC no se sale del buffer. +; Entrada: BC = bytes requeridos. Sale con BC intacto si hay espacio. +; --------------------------------------------------------------------------- +CALC_TEST_ROOM: + push hl + push de + ld hl, (FP_STKEND) + add hl, bc + ld de, FP_CALC_STACK_END + or a + sbc hl, de + pop de + pop hl + jr c, CALC_TEST_ROOM_OK + ld a, ERROR_OutOfMemory + jp __ERROR +CALC_TEST_ROOM_OK: + ret + +; --------------------------------------------------------------------------- +; THE 'TEST FIVE SPACES' SUBROUTINE ($33A9 TEST-5-SP) +; --------------------------------------------------------------------------- +L33A9: + push de + push hl + ld bc, 5 + call CALC_TEST_ROOM + pop hl + pop de + ret + +; --------------------------------------------------------------------------- +; STACK-NUM ($33B4) — apila un numero de 5 bytes apuntado por HL +; --------------------------------------------------------------------------- +L33B4: + ld de, (FP_STKEND) + call L33C0 + ld (FP_STKEND), de + ret + +; --------------------------------------------------------------------------- +; MOVE-FP / duplicate (literal $31, $33C0) +; --------------------------------------------------------------------------- +L33C0: + call L33A9 + ldir + ret + +; --------------------------------------------------------------------------- +; stk-data (literal $34, $33C6) / STK-CONST ($33C8) / STK-ZEROS ($33F1) +; --------------------------------------------------------------------------- +L33C6: + ld h, d + ld l, e + +L33C8: + call L33A9 + exx + push hl + exx + ex (sp), hl + push bc + ld a, (hl) + and $C0 + rlca + rlca + ld c, a + inc c + ld a, (hl) + and $3F + jr nz, L33DE + inc hl + ld a, (hl) +L33DE: + add a, $50 + ld (de), a + ld a, 5 + sub c + inc hl + inc de + ld b, 0 + ldir + pop bc + ex (sp), hl + exx + pop hl + exx + ld b, a + xor a +L33F1: + dec b + ret z + ld (de), a + inc de + jr L33F1 + +; --------------------------------------------------------------------------- +; SKIP-CONS ($33F7 / $33F8) +; --------------------------------------------------------------------------- +L33F7: + and a +L33F8: + ret z + push af + push de + ld de, 0 + call L33C8 + pop de + pop af + dec a + jr L33F8 + +; --------------------------------------------------------------------------- +; LOC-MEM ($3406) +; --------------------------------------------------------------------------- +L3406: + ld c, a + rlca + rlca + add a, c + ld c, a + ld b, 0 + add hl, bc + ret + +; --------------------------------------------------------------------------- +; get-mem-xx (literales $E0-$FF, $340F) +; --------------------------------------------------------------------------- +L340F: + push de + ld hl, (FP_MEM) + call L3406 + call L33C0 + pop hl + ret + +; --------------------------------------------------------------------------- +; stk-const-xx (literales $A0-$BF, $341B) + tabla de constantes +; --------------------------------------------------------------------------- +L341B: + ld h, d + ld l, e + exx + push hl + ld hl, L32C5 + exx + call L33F7 + call L33C8 + exx + pop hl + exx + ret + +; --------------------------------------------------------------------------- +; st-mem-xx (literales $C0-$DF, $342D) +; --------------------------------------------------------------------------- +L342D: + push hl + ex de, hl + ld hl, (FP_MEM) + call L3406 + ex de, hl + call L33C0 + ex de, hl + pop hl + ret + +; --------------------------------------------------------------------------- +; exchange (literal $01, $343C) +; --------------------------------------------------------------------------- +L343C: + ld b, 5 +L343E: + ld a, (de) + ld c, (hl) + ex de, hl + ld (de), a + ld (hl), c + inc hl + inc de + djnz L343E + ex de, hl + ret + +; --------------------------------------------------------------------------- +; series generator (literales $80-$9F, $3449) — necesario para SIN/COS/EXP/LN +; (aun sin las tablas de coeficientes; se añadirán en una fase posterior) +; --------------------------------------------------------------------------- +L3449: + ld b, a + call L335E + defb $31 ;;duplicate x,x + defb $0F ;;addition x+x + defb $C0 ;;st-mem-0 x+x + defb $02 ;;delete . + defb $A0 ;;stk-zero 0 + defb $C2 ;;st-mem-2 0 +L3453: + defb $31 ;;duplicate v,v. + defb $E0 ;;get-mem-0 v,v,x+2 + defb $04 ;;multiply v,v*x+2 + defb $E2 ;;get-mem-2 v,v*x+2,v + defb $C1 ;;st-mem-1 + defb $03 ;;subtract + defb $38 ;;end-calc + call L33C6 + call L3362 + defb $0F ;;addition + defb $01 ;;exchange + defb $C2 ;;st-mem-2 + defb $02 ;;delete + defb $35 ;;dec-jr-nz + defb (L3453 - $) & 0FFh ;;back to G-LOOP + defb $E1 ;;get-mem-1 + defb $03 ;;subtract + defb $38 ;;end-calc + ret + +; --------------------------------------------------------------------------- +; abs (literal $2A, $346A) / negate (literal $1B, $346E) / sgn (literal $29, +; $3492) +; --------------------------------------------------------------------------- +L346A: + ld b, $FF + jr L3474 + +L346E: + call L34E9 + ret c + ld b, 0 +L3474: + ld a, (hl) + and a + jr z, L3483 + inc hl + ld a, b + and $80 + or (hl) + rla + ccf + rra + ld (hl), a + dec hl + ret +L3483: + push de + push hl + call L2D7F + pop hl + ld a, b + or c + cpl + ld c, a + call L2D8E + pop de + ret + +L3492: + call L34E9 + ret c + push de + ld de, 1 + inc hl + rl (hl) + dec hl + sbc a, a + ld c, a + call L2D8E + pop de + ret + +; --------------------------------------------------------------------------- +; INT-FETCH ($2D7F) / INT-STORE ($2D8E) +; --------------------------------------------------------------------------- +L2D7F: + inc hl + ld c, (hl) + inc hl + ld a, (hl) + xor c + sub c + ld e, a + inc hl + ld a, (hl) + adc a, c + xor c + ld d, a + ret + +L2D8E: + push hl + ld (hl), 0 + inc hl + ld (hl), c + inc hl + ld a, e + xor c + sub c + ld (hl), a + inc hl + ld a, d + adc a, c + xor c + ld (hl), a + inc hl + ld (hl), 0 + pop hl + ret + +; --------------------------------------------------------------------------- +; PREP-ADD ($2F9B) +; --------------------------------------------------------------------------- +L2F9B: + ld a, (hl) + ld (hl), 0 + and a + ret z + inc hl + bit 7, (hl) + set 7, (hl) + dec hl + ret z + push bc + ld bc, 5 + add hl, bc + ld b, c + ld c, a + scf +L2FAF: + dec hl + ld a, (hl) + cpl + adc a, 0 + ld (hl), a + djnz L2FAF + ld a, c + pop bc + ret + +; --------------------------------------------------------------------------- +; FETCH-TWO ($2FBA) +; --------------------------------------------------------------------------- +L2FBA: + push hl + push af + ld c, (hl) + inc hl + ld b, (hl) + ld (hl), a + inc hl + ld a, c + ld c, (hl) + push bc + inc hl + ld c, (hl) + inc hl + ld b, (hl) + ex de, hl + ld d, a + ld e, (hl) + push de + inc hl + ld d, (hl) + inc hl + ld e, (hl) + push de + exx + pop de + pop hl + pop bc + exx + inc hl + ld d, (hl) + inc hl + ld e, (hl) + pop af + pop hl + ret + +; --------------------------------------------------------------------------- +; SHIFT-FP ($2FDD) / ADD-BACK ($3004) +; --------------------------------------------------------------------------- +L2FDD: + and a + ret z + cp $21 + jr nc, L2FF9 + push bc + ld b, a +L2FE5: + exx + sra l + rr d + rr e + exx + rr d + rr e + djnz L2FE5 + pop bc + ret nc + call L3004 + ret nz +L2FF9: + exx + xor a +L2FFB: + ld l, 0 + ld d, a + ld e, l + exx + ld de, 0 + ret + +L3004: + inc e + ret nz + inc d + ret nz + exx + inc e + jr nz, L300D + inc d +L300D: + exx + ret + +; --------------------------------------------------------------------------- +; subtract (literal $03, $300F) / addition (literal $0F, $3014) +; --------------------------------------------------------------------------- +L300F: + ex de, hl + call L346E + ex de, hl + +L3014: + ld a, (de) + or (hl) + jr nz, L303E + push de + inc hl + push hl + inc hl + ld e, (hl) + inc hl + ld d, (hl) + inc hl + inc hl + inc hl + ld a, (hl) + inc hl + ld c, (hl) + inc hl + ld b, (hl) + pop hl + ex de, hl + add hl, bc + ex de, hl + adc a, (hl) + rrca + adc a, 0 + jr nz, L303C + sbc a, a + ld (hl), a + inc hl + ld (hl), e + inc hl + ld (hl), d + dec hl + dec hl + dec hl + pop de + ret +L303C: + dec hl + pop de +L303E: + call L3293 + exx + push hl + exx + push de + push hl + call L2F9B + ld b, a + ex de, hl + call L2F9B + ld c, a + cp b + jr nc, L3055 + ld a, b + ld b, c + ex de, hl +L3055: + push af + sub b + call L2FBA + call L2FDD + pop af + pop hl + ld (hl), a + push hl + ld l, b + ld h, c + add hl, de + exx + ex de, hl + adc hl, bc + ex de, hl + ld a, h + adc a, l + ld l, a + rra + xor l + exx + ex de, hl + pop hl + rra + jr nc, L307C + ld a, 1 + call L2FDD + inc (hl) + jr z, L309F +L307C: + exx + ld a, l + and $80 + exx + inc hl + ld (hl), a + dec hl + jr z, L30A5 + ld a, e + neg + ccf + ld e, a + ld a, d + cpl + adc a, 0 + ld d, a + exx + ld a, e + cpl + adc a, 0 + ld e, a + ld a, d + cpl + adc a, 0 + jr nc, L30A3 + rra + exx + inc (hl) +L309F: + jp z, L31AD + exx +L30A3: + ld d, a + exx +L30A5: + xor a + jp L3155 + +; --------------------------------------------------------------------------- +; HL-HL*DE ($30A9) / PREP-M/D ($30C0) +; --------------------------------------------------------------------------- +L30A9: + push bc + ld b, 16 + ld a, h + ld c, l + ld hl, 0 +L30B1: + add hl, hl + jr c, L30BE + rl c + rla + jr nc, L30BC + add hl, de + jr c, L30BE +L30BC: + djnz L30B1 +L30BE: + pop bc + ret + +L30C0: + call L34E9 + ret c + inc hl + xor (hl) + set 7, (hl) + dec hl + ret + +; --------------------------------------------------------------------------- +; multiply (literal $04, $30CA) +; --------------------------------------------------------------------------- +L30CA: + ld a, (de) + or (hl) + jr nz, L30F0 + push de + push hl + push de + call L2D7F + ex de, hl + ex (sp), hl + ld b, c + call L2D7F + ld a, b + xor c + ld c, a + pop hl + call L30A9 + ex de, hl + pop hl + jr c, L30EF + ld a, d + or e + jr nz, L30EA + ld c, a +L30EA: + call L2D8E + pop de + ret +L30EF: + pop de +L30F0: + call L3293 + xor a + call L30C0 + ret c + exx + push hl + exx + push de + ex de, hl + call L30C0 + ex de, hl + jr c, L315D + push hl + call L2FBA + ld a, b + and a + sbc hl, hl + exx + push hl + sbc hl, hl + exx + ld b, $21 + jr L3125 +L3114: + jr nc, L311B + add hl, de + exx + adc hl, de + exx +L311B: + exx + rr h + rr l + exx + rr h + rr l +L3125: + exx + rr b + rr c + exx + rr c + rra + djnz L3114 + ex de, hl + exx + ex de, hl + exx + pop bc + pop hl + ld a, b + add a, c + jr nz, L313B + and a +L313B: + dec a + ccf +L313D: + rla + ccf + rra + jp p, L3146 + jr nc, L31AD + and a +L3146: + inc a + jr nz, L3151 + jr c, L3151 + exx + bit 7, d + exx + jr nz, L31AD +L3151: + ld (hl), a + exx + ld a, b + exx +L3155: + jr nc, L316C + ld a, (hl) + and a +L3159: + ld a, $80 + jr z, L315E +L315D: + xor a +L315E: + exx + and d + call L2FFB + rlca + ld (hl), a + jr c, L3195 + inc hl + ld (hl), a + dec hl + jr L3195 +L316C: + ld b, $20 +L316E: + exx + bit 7, d + exx + jr nz, L3186 + rlca + rl e + rl d + exx + rl e + rl d + exx + dec (hl) + jr z, L3159 + djnz L316E + jr L315D +L3186: + rla + jr nc, L3195 + call L3004 + jr nz, L3195 + exx + ld d, $80 + exx + inc (hl) + jr z, L31AD +L3195: + push hl + inc hl + exx + push de + exx + pop bc + ld a, b + rla + rl (hl) + rra + ld (hl), a + inc hl + ld (hl), c + inc hl + ld (hl), d + inc hl + ld (hl), e + pop hl + pop de + exx + pop hl + exx + ret + +L31AD: + ld a, ERROR_NumberTooBig + jp __ERROR + +; --------------------------------------------------------------------------- +; division (literal $05, $31AF) +; --------------------------------------------------------------------------- +L31AF: + call L3293 + ex de, hl + xor a + call L30C0 + jr c, L31AD + ex de, hl + call L30C0 + ret c + exx + push hl + exx + push de + push hl + call L2FBA + exx + push hl + ld h, b + ld l, c + exx + ld h, c + ld l, b + xor a + ld b, $DF + jr L31E2 +L31D2: + rla + rl c + exx + rl c + rl b + exx +L31DB: + add hl, hl + exx + adc hl, hl + exx + jr c, L31F2 +L31E2: + sbc hl, de + exx + sbc hl, de + exx + jr nc, L31F9 + add hl, de + exx + adc hl, de + exx + and a + jr L31FA +L31F2: + and a + sbc hl, de + exx + sbc hl, de + exx +L31F9: + scf +L31FA: + inc b + jp m, L31D2 + push af + jr z, L31E2 + ld e, a + ld d, c + exx + ld e, c + ld d, b + pop af + rr b + pop af + rr b + exx + pop bc + pop hl + ld a, b + sub c + jp L313D + +; --------------------------------------------------------------------------- +; Integer truncation towards zero (literal $3A, $3214) +; --------------------------------------------------------------------------- +L3214: + ld a, (hl) + and a + ret z + cp $81 + jr nc, L3221 + ld (hl), 0 + ld a, $20 + jr L3272 +L3221: + cp $91 + jr nz, L323F + inc hl + inc hl + inc hl + ld a, $80 + and (hl) + dec hl + or (hl) + dec hl + jr nz, L3233 + ld a, $80 + xor (hl) +L3233: + dec hl + jr nz, L326C + ld (hl), a + inc hl + ld (hl), $FF + dec hl + ld a, $18 + jr L3272 +L323F: + jr nc, L326D + push de + cpl + add a, $91 + inc hl + ld d, (hl) + inc hl + ld e, (hl) + dec hl + dec hl + ld c, 0 + bit 7, d + jr z, L3252 + dec c +L3252: + set 7, d + ld b, 8 + sub b + add a, b + jr c, L325E + ld e, d + ld d, 0 + sub b +L325E: + jr z, L3267 + ld b, a +L3261: + srl d + rr e + djnz L3261 +L3267: + call L2D8E + pop de + ret +L326C: + ld a, (hl) +L326D: + sub $A0 + ret p + neg +L3272: + push de + ex de, hl + dec hl + ld b, a + srl b + srl b + srl b + jr z, L3283 +L327E: + ld (hl), 0 + dec hl + djnz L327E +L3283: + and $07 + jr z, L3290 + ld b, a + ld a, $FF +L328A: + sla a + djnz L328A + and (hl) + ld (hl), a +L3290: + ex de, hl + pop de + ret + +; --------------------------------------------------------------------------- +; RE-ST-TWO ($3293) / RESTK-SUB ($3296) / re-stack (literal $3D, $3297) +; --------------------------------------------------------------------------- +L3293: + call L3296 +L3296: + ex de, hl +L3297: + ld a, (hl) + and a + ret nz + push de + call L2D7F + xor a + inc hl + ld (hl), a + dec hl + ld (hl), a + ld b, $91 + ld a, d + and a + jr nz, L32B1 + or e + ld b, d + jr z, L32BD + ld d, e + ld e, b + ld b, $89 +L32B1: + ex de, hl +L32B2: + dec b + add hl, hl + jr nc, L32B2 + rrc c + rr h + rr l + ex de, hl +L32BD: + dec hl + ld (hl), e + dec hl + ld (hl), d + dec hl + ld (hl), b + pop de + ret + +; --------------------------------------------------------------------------- +; THE 'TABLE OF CONSTANTS' ($32C5-$32D6) +; --------------------------------------------------------------------------- +L32C5: ;;stk-zero + defb $00, $B0, $00 +L32C8: ;;stk-one + defb $40, $B0, $00, $01 +L32CC: ;;stk-half + defb $30, $00 +L32CE: ;;stk-pi/2 + defb $F1, $49, $0F, $DA, $A2 +L32D3: ;;stk-ten + defb $40, $B0, $00, $0A + +; --------------------------------------------------------------------------- +; THE 'TABLE OF ADDRESSES' ($32D7) — tbl-addrs +; +; Las entradas para funciones no soportadas (cadenas, USR, PEEK, IN, CODE, +; LEN, READ-IN, VAL$) apuntan a CALC_UNSUPPORTED, que detiene la ejecucion +; con un error claro si alguna vez se generase ese literal (no deberia +; ocurrir: el compilador de ZX BASIC no emite esos literales para nuestro +; runtime, ver la lista de literales usados en arith/cmp/bool/math/*.asm). +; --------------------------------------------------------------------------- +L32D7: + defw L368F ; $00 jump-true + defw L343C ; $01 exchange + defw L33A1 ; $02 delete + defw L300F ; $03 subtract + defw L30CA ; $04 multiply + defw L31AF ; $05 division + defw L3851 ; $06 to-power + defw L351B ; $07 or + defw L3524 ; $08 no-&-no + defw L353B ; $09 no-l-eql + defw L353B ; $0A no-gr-eql + defw L353B ; $0B nos-neql + defw L353B ; $0C no-grtr + defw L353B ; $0D no-less + defw L353B ; $0E nos-eql + defw L3014 ; $0F addition + defw CALC_UNSUPPORTED ; $10 str-&-no + defw CALC_UNSUPPORTED ; $11 str-l-eql + defw CALC_UNSUPPORTED ; $12 str-gr-eql + defw CALC_UNSUPPORTED ; $13 strs-neql + defw CALC_UNSUPPORTED ; $14 str-grtr + defw CALC_UNSUPPORTED ; $15 str-less + defw CALC_UNSUPPORTED ; $16 strs-eql + defw CALC_UNSUPPORTED ; $17 strs-add + defw CALC_UNSUPPORTED ; $18 val$ + defw CALC_UNSUPPORTED ; $19 usr-$ + defw CALC_UNSUPPORTED ; $1A read-in + defw L346E ; $1B negate + defw CALC_UNSUPPORTED ; $1C code + defw CALC_UNSUPPORTED ; $1D val (pendiente: parseo numerico propio) + defw CALC_UNSUPPORTED ; $1E len + defw L37B5 ; $1F sin + defw L37AA ; $20 cos + defw L37DA ; $21 tan + defw L3833 ; $22 asn + defw L3843 ; $23 acs + defw L37E2 ; $24 atn + defw L3713 ; $25 ln + defw L36C4 ; $26 exp + defw L36AF ; $27 int + defw L384A ; $28 sqr + defw L3492 ; $29 sgn + defw L346A ; $2A abs + defw CALC_UNSUPPORTED ; $2B peek + defw CALC_UNSUPPORTED ; $2C in + defw CALC_UNSUPPORTED ; $2D usr-no + defw CALC_UNSUPPORTED ; $2E str$ (pendiente) + defw CALC_UNSUPPORTED ; $2F chr$ + defw L3501 ; $30 not + defw L33C0 ; $31 duplicate + defw L36A0 ; $32 n-mod-m + defw L3686 ; $33 jump + defw L33C6 ; $34 stk-data + defw L367A ; $35 dec-jr-nz + defw L3506 ; $36 less-0 + defw L34F9 ; $37 greater-0 + defw L369B ; $38 end-calc + defw L3783 ; $39 get-argt + defw L3214 ; $3A truncate + defw L33A2 ; $3B fp-calc-2 + defw CALC_UNSUPPORTED ; $3C e-to-fp + defw L3297 ; $3D re-stack + defw L3449 ; series-xx $80-$9F + defw L341B ; stk-const-xx $A0-$BF + defw L342D ; st-mem-xx $C0-$DF + defw L340F ; get-mem-xx $E0-$FF + +CALC_UNSUPPORTED: + ld a, ERROR_InvalidArg + jp __ERROR + +; --------------------------------------------------------------------------- +; THE 'CALCULATE' SUBROUTINE ($335B) — motor principal +; --------------------------------------------------------------------------- +L335B: + call L35BF +L335E: + ld a, b + ld (FP_BREG), a +L3362: + exx + ex (sp), hl + exx +L3365: + ld (FP_STKEND), de + exx + ld a, (hl) + inc hl +L336C: + push hl + and a + jp p, L3380 + ld d, a + and $60 + rrca + rrca + rrca + rrca + add a, $7C + ld l, a + ld a, d + and $1F + jr L338E +L3380: + cp $18 + jr nc, L338C + exx + ld bc, $FFFB + ld d, h + ld e, l + add hl, bc + exx +L338C: + rlca + ld l, a +L338E: + ld de, L32D7 + ld h, 0 + add hl, de + ld e, (hl) + inc hl + ld d, (hl) + ld hl, L3365 + ex (sp), hl + push de + exx + ld bc, (FP_STKEND + 1) ; C=STKEND_hi, B=FP_BREG (ver nota en sysvars.asm) + ret + +; --------------------------------------------------------------------------- +; delete (literal $02) — un simple RET; tambien destino de salto indirecto +; --------------------------------------------------------------------------- +L33A1: + ret + +; --------------------------------------------------------------------------- +; fp-calc-2 (literal $3B) — reentrada de un solo literal +; --------------------------------------------------------------------------- +L33A2: + pop af + ld a, (FP_BREG) + exx + jr L336C + +; --------------------------------------------------------------------------- +; STK-PNTRS ($35BF) +; --------------------------------------------------------------------------- +L35BF: + ld hl, (FP_STKEND) + ld de, $FFFB + push hl + add hl, de + pop de + ret + +; --------------------------------------------------------------------------- +; jump-true (literal $00) / jump (literal $33) / dec-jr-nz (literal $35) +; --------------------------------------------------------------------------- +L368F: + inc de + inc de + ld a, (de) + dec de + dec de + and a + jr nz, L3686 + exx + inc hl + exx + ret + +L367A: + exx + push hl + ld hl, FP_BREG + dec (hl) + pop hl + jr nz, L3687 + inc hl + exx + ret + +L3686: + exx +L3687: + ld e, (hl) + ld a, e + rla + sbc a, a + ld d, a + add hl, de + exx + ret + +; --------------------------------------------------------------------------- +; end-calc (literal $38) +; --------------------------------------------------------------------------- +L369B: + pop af + exx + ex (sp), hl + exx + ret + +; --------------------------------------------------------------------------- +; n-mod-m (literal $32) — implementado como programa del propio calculador +; --------------------------------------------------------------------------- +L36A0: + rst 28h + defb $C0, $02, $31, $E0, $05, $27, $E0, $01, $C0, $04, $03, $E0, $38 + ret + +; --------------------------------------------------------------------------- +; int (literal $27) — implementado como programa del propio calculador +; --------------------------------------------------------------------------- +L36AF: + rst 28h + defb $31 ;;duplicate + defb $36 ;;less-0 + defb $00 ;;jump-true + defb (L36B7 - $) & 0FFh ;;a X-NEG + defb $3A ;;truncate + defb $38 ;;end-calc + ret +L36B7: + defb $31 ;;duplicate + defb $3A ;;truncate + defb $C0 ;;st-mem-0 + defb $03 ;;subtract + defb $E0 ;;get-mem-0 + defb $01 ;;exchange + defb $30 ;;not + defb $00 ;;jump-true + defb (L36C2 - $) & 0FFh ;;a EXIT + defb $A1 ;;stk-one + defb $03 ;;subtract +L36C2: + defb $38 ;;end-calc + +; --------------------------------------------------------------------------- +; TEST-ZERO ($34E9) / GREATER-0 (literal $37) / NOT (literal $30) / +; less-0 (literal $36) / SIGN-TO-C / FP-0/1 +; --------------------------------------------------------------------------- +L34E9: + push hl + push bc + ld b, a + ld a, (hl) + inc hl + or (hl) + inc hl + or (hl) + inc hl + or (hl) + ld a, b + pop bc + pop hl + ret nz + scf + ret + +L34F9: + call L34E9 + ret c + ld a, $FF + jr L3507 + +L3501: + call L34E9 + jr L350B + +L3506: + xor a +L3507: + inc hl + xor (hl) + dec hl + rlca +L350B: + push hl + ld a, 0 + ld (hl), a + inc hl + ld (hl), a + inc hl + rla + ld (hl), a + rra + inc hl + ld (hl), a + inc hl + ld (hl), a + pop hl + ret + +; --------------------------------------------------------------------------- +; or (literal $07) / no-&-no (literal $08) +; --------------------------------------------------------------------------- +L351B: + ex de, hl + call L34E9 + ex de, hl + ret c + scf + jr L350B + +L3524: + ex de, hl + call L34E9 + ex de, hl + ret nc + and a + jr L350B + +; --------------------------------------------------------------------------- +; comparaciones numericas (literales $09-$0E, $353B) — solo la rama numerica; +; no se soportan comparaciones de cadenas via calculador en este runtime. +; --------------------------------------------------------------------------- +L353B: + ld a, b + sub 8 + bit 2, a + jr nz, L3543 + dec a +L3543: + rrca + jr nc, L354E + push af + push hl + call L343C + pop de + ex de, hl + pop af +L354E: + rrca + push af + call L300F + jr L358C + +; --------------------------------------------------------------------------- +; END-TESTS ($358C) +; --------------------------------------------------------------------------- +L358C: + pop af + push af + call c, L3501 + pop af + push af + call nc, L34F9 + pop af + rrca + call nc, L3501 + ret + +; =========================================================================== +; FASE 4: SIN/COS/TAN/ASN/ACS/ATN/LN/EXP/SQR +; +; Todas estas funciones son, igual que "int" o "n-mod-m" mas arriba, +; programas del propio calculador (rst 28h + bytes de literal), identicos a +; los de la ROM, apoyados en el "series generator" (L3449/L3453) ya portado. +; Solo se han corregido los offsets de jump-true/jump (recalculados con la +; misma formula ya usada en el resto del fichero: (destino - $) & 0FFh) y se +; ha sustituido "RST 08h ; DEFB " (ERROR-1 de la ROM) por el +; mecanismo de error propio (ERROR_xxx + jp __ERROR). +; =========================================================================== + +; --------------------------------------------------------------------------- +; STACK-A ($2D28) / STACK-BC ($2D2B) — apila A (o BC) como entero pequeño +; --------------------------------------------------------------------------- +L2D28: + ld c, a + ld b, 0 +L2D2B: + xor a + ld e, a + ld d, c + ld c, b + ld b, a + call __FPSTACK_PUSH + rst 28h + defb $38 ;;end-calc (recalcula HL/DE tras el push) + and a + ret + +; --------------------------------------------------------------------------- +; FP-TO-BC ($2DA2) — recoge el ultimo valor de la pila FP en BC (redondeando) +; --------------------------------------------------------------------------- +L2DA2: + rst 28h + defb $38 ;;end-calc -> HL apunta al ultimo valor + ld a, (hl) + and a + jr z, L2DAD + rst 28h + defb $A2 ;;stk-half + defb $0F ;;addition + defb $27 ;;int + defb $38 ;;end-calc +L2DAD: + rst 28h + defb $02 ;;delete + defb $38 ;;end-calc + push hl + push de + ex de, hl + ld b, (hl) + call L2D7F + xor a + sub b + bit 7, c + ld b, d + ld c, e + ld a, e + pop de + pop hl + ret + +; --------------------------------------------------------------------------- +; FP-TO-A ($2DD5) — como FP-TO-BC pero devuelve A, con overflow en carry +; --------------------------------------------------------------------------- +L2DD5: + call L2DA2 + ret c + push af + dec b + inc b + jr z, L2DE1 + pop af + scf + ret +L2DE1: + pop af + ret + +; --------------------------------------------------------------------------- +; get-argt (literal $39, $3783) — reduce el argumento de sin/cos a -1..+1 +; --------------------------------------------------------------------------- +L3783: + rst 28h + defb $3D ;;re-stack + defb $34 ;;stk-data + defb $EE ;;Exponent: $7E, Bytes: 4 + defb $22, $F9, $83, $6E + defb $04 ;;multiply + defb $31 ;;duplicate + defb $A2 ;;stk-half + defb $0F ;;addition + defb $27 ;;int + defb $03 ;;subtract + defb $31 ;;duplicate + defb $0F ;;addition + defb $31 ;;duplicate + defb $0F ;;addition + defb $31 ;;duplicate + defb $2A ;;abs + defb $A1 ;;stk-one + defb $03 ;;subtract + defb $31 ;;duplicate + defb $37 ;;greater-0 + defb $C0 ;;st-mem-0 + defb $00 ;;jump-true + defb (L37A1 - $) & 0FFh ;;a ZPLUS + defb $02 ;;delete + defb $38 ;;end-calc + ret +L37A1: + defb $A1 ;;stk-one + defb $03 ;;subtract + defb $01 ;;exchange + defb $36 ;;less-0 + defb $00 ;;jump-true + defb (L37A8 - $) & 0FFh ;;a YNEG + defb $1B ;;negate +L37A8: + defb $38 ;;end-calc + ret + +; --------------------------------------------------------------------------- +; cos (literal $20, $37AA) — cae en sin/C-ENT (codigo compartido) +; --------------------------------------------------------------------------- +L37AA: + rst 28h + defb $39 ;;get-argt + defb $2A ;;abs + defb $A1 ;;stk-one + defb $03 ;;subtract + defb $E0 ;;get-mem-0 + defb $00 ;;jump-true + defb (L37B7 - $) & 0FFh ;;a C-ENT + defb $1B ;;negate + defb $33 ;;jump + defb (L37B7 - $) & 0FFh ;;a C-ENT + +; --------------------------------------------------------------------------- +; sin (literal $1F, $37B5) / C-ENT ($37B7, compartido con cos) +; --------------------------------------------------------------------------- +L37B5: + rst 28h + defb $39 ;;get-argt +L37B7: + defb $31 ;;duplicate + defb $31 ;;duplicate + defb $04 ;;multiply + defb $31 ;;duplicate + defb $0F ;;addition + defb $A1 ;;stk-one + defb $03 ;;subtract + defb $86 ;;series-06 + defb $14, $E6 + defb $5C, $1F, $0B + defb $A3, $8F, $38, $EE + defb $E9, $15, $63, $BB, $23 + defb $EE, $92, $0D, $CD, $ED + defb $F1, $23, $5D, $1B, $EA + defb $04 ;;multiply + defb $38 ;;end-calc + ret + +; --------------------------------------------------------------------------- +; tan (literal $21, $37DA) — sin(x) / cos(x) +; --------------------------------------------------------------------------- +L37DA: + rst 28h + defb $31 ;;duplicate + defb $1F ;;sin + defb $01 ;;exchange + defb $20 ;;cos + defb $05 ;;division + defb $38 ;;end-calc + ret + +; --------------------------------------------------------------------------- +; atn (literal $24, $37E2) +; --------------------------------------------------------------------------- +L37E2: + call L3297 ; re-stack + ld a, (hl) + cp $81 + jr c, L37F8 ; SMALL + rst 28h + defb $A1 ;;stk-one + defb $1B ;;negate + defb $01 ;;exchange + defb $05 ;;division + defb $31 ;;duplicate + defb $36 ;;less-0 + defb $A3 ;;stk-pi/2 + defb $01 ;;exchange + defb $00 ;;jump-true + defb (L37FA - $) & 0FFh ;;a CASES + defb $1B ;;negate + defb $33 ;;jump + defb (L37FA - $) & 0FFh ;;a CASES +L37F8: + rst 28h + defb $A0 ;;stk-zero +L37FA: + defb $01 ;;exchange + defb $31 ;;duplicate + defb $31 ;;duplicate + defb $04 ;;multiply + defb $31 ;;duplicate + defb $0F ;;addition + defb $A1 ;;stk-one + defb $03 ;;subtract + defb $8C ;;series-0C + defb $10, $B2 + defb $13, $0E + defb $55, $E4, $8D + defb $58, $39, $BC + defb $5B, $98, $FD + defb $9E, $00, $36, $75 + defb $A0, $DB, $E8, $B4 + defb $63, $42, $C4 + defb $E6, $B5, $09, $36, $BE + defb $E9, $36, $73, $1B, $5D + defb $EC, $D8, $DE, $63, $BE + defb $F0, $61, $A1, $B3, $0C + defb $04 ;;multiply + defb $0F ;;addition + defb $38 ;;end-calc + ret + +; --------------------------------------------------------------------------- +; asn (literal $22, $3833) +; --------------------------------------------------------------------------- +L3833: + rst 28h + defb $31 ;;duplicate + defb $31 ;;duplicate + defb $04 ;;multiply + defb $A1 ;;stk-one + defb $03 ;;subtract + defb $1B ;;negate + defb $28 ;;sqr + defb $A1 ;;stk-one + defb $0F ;;addition + defb $05 ;;division + defb $24 ;;atn + defb $31 ;;duplicate + defb $0F ;;addition + defb $38 ;;end-calc + ret + +; --------------------------------------------------------------------------- +; acs (literal $23, $3843) +; --------------------------------------------------------------------------- +L3843: + rst 28h + defb $22 ;;asn + defb $A3 ;;stk-pi/2 + defb $03 ;;subtract + defb $1B ;;negate + defb $38 ;;end-calc + ret + +; --------------------------------------------------------------------------- +; ln (literal $25, $3713) +; --------------------------------------------------------------------------- +L3713: + rst 28h + defb $3D ;;re-stack + defb $31 ;;duplicate + defb $37 ;;greater-0 + defb $00 ;;jump-true + defb (L371C - $) & 0FFh ;;a VALID + defb $38 ;;end-calc + ld a, ERROR_InvalidArg + jp __ERROR +L371C: + defb $A0 ;;stk-zero + defb $02 ;;delete + defb $38 ;;end-calc + ld a, (hl) + ld (hl), $80 + call L2D28 + rst 28h + defb $34 ;;stk-data + defb $38 ;;Exponent: $88, Bytes: 1 + defb $00 + defb $03 ;;subtract + defb $01 ;;exchange + defb $31 ;;duplicate + defb $34 ;;stk-data + defb $F0 ;;Exponent: $80, Bytes: 4 + defb $4C, $CC, $CC, $CD + defb $03 ;;subtract + defb $37 ;;greater-0 + defb $00 ;;jump-true + defb (L373D - $) & 0FFh ;;a GRE.8 + defb $01 ;;exchange + defb $A1 ;;stk-one + defb $03 ;;subtract + defb $01 ;;exchange + defb $38 ;;end-calc + inc (hl) + rst 28h +L373D: + defb $01 ;;exchange + defb $34 ;;stk-data + defb $F0 ;;Exponent: $80, Bytes: 4 + defb $31, $72, $17, $F8 + defb $04 ;;multiply + defb $01 ;;exchange + defb $A2 ;;stk-half + defb $03 ;;subtract + defb $A2 ;;stk-half + defb $03 ;;subtract + defb $31 ;;duplicate + defb $34 ;;stk-data + defb $32 ;;Exponent: $82, Bytes: 1 + defb $20 + defb $04 ;;multiply + defb $A2 ;;stk-half + defb $03 ;;subtract + defb $8C ;;series-0C + defb $11, $AC + defb $14, $09 + defb $56, $DA, $A5 + defb $59, $30, $C5 + defb $5C, $90, $AA + defb $9E, $70, $6F, $61 + defb $A1, $CB, $DA, $96 + defb $A4, $31, $9F, $B4 + defb $E7, $A0, $FE, $5C, $FC + defb $EA, $1B, $43, $CA, $36 + defb $ED, $A7, $9C, $7E, $5E + defb $F0, $6E, $23, $80, $93 + defb $04 ;;multiply + defb $0F ;;addition + defb $38 ;;end-calc + ret + +; --------------------------------------------------------------------------- +; exp (literal $26, $36C4) +; --------------------------------------------------------------------------- +L36C4: + rst 28h + defb $3D ;;re-stack + defb $34 ;;stk-data + defb $F1 ;;Exponent: $81, Bytes: 4 + defb $38, $AA, $3B, $29 + defb $04 ;;multiply + defb $31 ;;duplicate + defb $27 ;;int + defb $C3 ;;st-mem-3 + defb $03 ;;subtract + defb $31 ;;duplicate + defb $0F ;;addition + defb $A1 ;;stk-one + defb $03 ;;subtract + defb $88 ;;series-08 + defb $13, $36 + defb $58, $65, $66 + defb $9D, $78, $65, $40 + defb $A2, $60, $32, $C9 + defb $E7, $21, $F7, $AF, $24 + defb $EB, $2F, $B0, $B0, $14 + defb $EE, $7E, $BB, $94, $58 + defb $F1, $3A, $7E, $F8, $CF + defb $E3 ;;get-mem-3 + defb $38 ;;end-calc + call L2DD5 + jr nz, L3705 ; N-NEGTV + jr c, L3703 ; REPORT-6b + add a, (hl) + jr nc, L370C ; RESULT-OK +L3703: + ld a, ERROR_NumberTooBig + jp __ERROR +L3705: + jr c, L370E ; RSLT-ZERO + sub (hl) + jr nc, L370E ; RSLT-ZERO + neg +L370C: + ld (hl), a + ret +L370E: + rst 28h + defb $02 ;;delete + defb $A0 ;;stk-zero + defb $38 ;;end-calc + ret + +; --------------------------------------------------------------------------- +; sqr (literal $28, $384A) — cae en to-power (codigo compartido) +; --------------------------------------------------------------------------- +L384A: + rst 28h + defb $31 ;;duplicate + defb $30 ;;not + defb $00 ;;jump-true + defb (L386C - $) & 0FFh ;;a LAST + defb $A2 ;;stk-half + defb $38 ;;end-calc + +; --------------------------------------------------------------------------- +; to-power (literal $06, $3851) +; --------------------------------------------------------------------------- +L3851: + rst 28h + defb $01 ;;exchange + defb $31 ;;duplicate + defb $30 ;;not + defb $00 ;;jump-true + defb (L385D - $) & 0FFh ;;a XIS0 + defb $25 ;;ln + defb $04 ;;multiply + defb $38 ;;end-calc + jp L36C4 +L385D: + defb $02 ;;delete + defb $31 ;;duplicate + defb $30 ;;not + defb $00 ;;jump-true + defb (L386A - $) & 0FFh ;;a ONE + defb $A0 ;;stk-zero + defb $01 ;;exchange + defb $37 ;;greater-0 + defb $00 ;;jump-true + defb (L386C - $) & 0FFh ;;a LAST + defb $A1 ;;stk-one + defb $01 ;;exchange + defb $05 ;;division +L386A: + defb $02 ;;delete + defb $A1 ;;stk-one +L386C: + defb $38 ;;end-calc + ret + +; =========================================================================== +; FASE 5: STK-TO-A / STK-TO-BC / CD-PRMS1 +; +; Rutinas auxiliares de la ROM usadas por DRAW3 (modo arco) y CIRCLE-DRAW. +; No son literales del calculador (no se invocan via rst 28h + defb), sino +; rutinas normales que a su vez usan el calculador ya portado (FP-TO-A, +; STACK-A, y los literales sqr/sin/stk-data/etc de fp_calc.asm). +; =========================================================================== + +; --------------------------------------------------------------------------- +; STK-TO-A ($2314) — comprime el ultimo valor de la pila FP en A. +; C = $01 si es positivo o cero, $FF si es negativo. +; Error IntOutOfRange (sustituye a REPORT-Bc / RST 08h) si >= 256. +; --------------------------------------------------------------------------- +L2314: + call L2DD5 ; FP-TO-A: A = valor comprimido, Z si signo positivo + jr c, L24F9 + ld c, $01 + ret z + ld c, $FF + ret + +L24F9: + ld a, ERROR_IntOutOfRange + jp __ERROR + +; --------------------------------------------------------------------------- +; STK-TO-BC ($2307) — recoge dos valores de la pila FP: el primero (mas +; antiguo) en BC, el segundo (mas reciente) en DE (bajo, con signo en E/D). +; --------------------------------------------------------------------------- +L2307: + call L2314 + ld b, a + push bc + call L2314 + ld e, c + pop bc + ld d, c + ld c, a + ret + +; --------------------------------------------------------------------------- +; CD-PRMS1 ($247D) — CIRCLE/DRAW PARAMETERS: a partir del "diametro" z (tope +; de pila) y el angulo total en mem-5, calcula el numero de lineas rectas +; (B, multiplo de 4, max 252) y deja en mem-1/mem-3/mem-4 sin(a/2), cos(a) y +; sin(a) del angulo de paso "a" = ANGULO/lineas. +; --------------------------------------------------------------------------- +L247D: + rst 28h + defb $31 ;;duplicate z, z. + defb $28 ;;sqr z, sqr(z). + defb $34 ;;stk-data z, sqr(z), 2. + defb $32 ;;Exponent: $82, Bytes: 1 + defb $00 ;;(+00,+00,+00) + defb $01 ;;exchange z, 2, sqr(z). + defb $05 ;;division z, 2/sqr(z). + defb $E5 ;;get-mem-5 z, 2/sqr(z), ANGLE. + defb $01 ;;exchange z, ANGLE, 2/sqr(z) + defb $05 ;;division z, ANGLE*sqr(z)/2 (=num. lineas) + defb $2A ;;abs (solo para arco) + defb $38 ;;end-calc z, numero de lineas. + + call L2DD5 ; FP-TO-A + jr c, L247D_USE252 + + and $FC ; multiplo de 4 (p.ej. 29 -> 28) + add a, $04 ; podria dar overflow -> 256 + jr nc, L247D_SAVE + +L247D_USE252: + ld a, $FC ; limite de 252 (para arco) + +L247D_SAVE: + push af ; conserva el contador de lineas + call L2D28 ; apila el contador modificado + + rst 28h + defb $E5 ;;get-mem-5 z, A, ANGLE. + defb $01 ;;exchange z, ANGLE, A. + defb $05 ;;division z, ANGLE/A. (angulo de paso = a) + defb $31 ;;duplicate z, a, a. + defb $1F ;;sin z, a, sin(a) + defb $C4 ;;st-mem-4 z, a, sin(a) + defb $02 ;;delete z, a. + defb $31 ;;duplicate z, a, a. + defb $A2 ;;stk-half z, a, a, 1/2. + defb $04 ;;multiply z, a, a/2. + defb $1F ;;sin z, a, sin(a/2). + defb $C1 ;;st-mem-1 z, a, sin(a/2). + defb $01 ;;exchange z, sin(a/2), a. + defb $C0 ;;st-mem-0 z, sin(a/2), a. (solo para arco) + defb $02 ;;delete z, sin(a/2). + defb $31 ;;duplicate z, sin(a/2), sin(a/2). + defb $04 ;;multiply z, sin(a/2)^2. + defb $31 ;;duplicate z, sin(a/2)^2, sin(a/2)^2. + defb $0F ;;addition z, 2*sin(a/2)^2. + defb $A1 ;;stk-one z, 2*sin(a/2)^2, 1. + defb $03 ;;subtract z, 2*sin(a/2)^2-1. + defb $1B ;;negate z, 1-2*sin(a/2)^2 = cos(a). + defb $C3 ;;st-mem-3 z, cos(a). + defb $02 ;;delete z. + defb $38 ;;end-calc z. + + pop bc ; restaura el contador de lineas ret pop namespace diff --git a/src/lib/arch/zx81sd/runtime/fp_tostr.asm b/src/lib/arch/zx81sd/runtime/fp_tostr.asm new file mode 100644 index 000000000..50d379bc5 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/fp_tostr.asm @@ -0,0 +1,298 @@ +; fp_tostr.asm (zx81sd) — Conversión de un FLOAT a texto ASCII decimal +; +; Usado por printf.asm (PRINT de un FLOAT) y str.asm (STR$) en zx81sd. +; +; La ROM del Spectrum resuelve esto con PRINT-FP, una rutina enorme que +; además soporta notación científica (E-format) y se apoya en mecanismos +; ajenos a zx81sd (canales CHAN-OPEN, área de "workspace" del editor BASIC +; que crece con BC-SPACES). Aquí, por acuerdo explícito, se implementa una +; versión simplificada: signo + parte entera + hasta 5 decimales, sin +; notación científica, recortando ceros finales (y el punto si todos los +; decimales resultan cero). Cubre el uso habitual de PRINT/STR$ con FLOAT. +; +; Se apoya en el calculador ya portado (fp_calc.asm: duplicate/int/subtract/ +; negate) y en rutinas Z80 puras ya existentes y verificadas en zx81sd: +; __FTOU32REG/__FTOU8 (conversión FLOAT -> entero, sin ROM) y __DIVU32. + +#include once +#include once +#include once +#include once + + push namespace core + +; --- Variables de trabajo (no reentrante, uso transitorio) ------------------ +FP_STR_ORIG: defb 0, 0, 0, 0, 0 ; A E D C B del valor (se vuelve positivo) +FP_STR_INT: defb 0, 0, 0, 0, 0 ; parte entera +FP_STR_FRAC: defb 0, 0, 0, 0, 0 ; parte fraccionaria (se actualiza cada iteración) +FP_STR_COUNT: defb 0 ; contador de decimales restantes +FP_STR_WR: defw 0 ; cursor de escritura en FP_STR_BUF +FP_STR_BUF: defs 24 ; buffer de salida (signo + entero + '.' + decimales) + +; --------------------------------------------------------------------------- +; STORE5 — Guarda A,E,D,C,B en (HL),(HL+1)..(HL+4) +; LOAD5 — Carga A,E,D,C,B desde (HL),(HL+1)..(HL+4) +; (Z80 no tiene LD E,(nn)/LD D,(nn)/etc, solo LD A,(nn) y LD rr,(nn) con pares; +; por eso se accede siempre indirectamente vía HL.) +; --------------------------------------------------------------------------- +STORE5: + ld (hl), a + inc hl + ld (hl), e + inc hl + ld (hl), d + inc hl + ld (hl), c + inc hl + ld (hl), b + ret + +LOAD5: + ld a, (hl) + inc hl + ld e, (hl) + inc hl + ld d, (hl) + inc hl + ld c, (hl) + inc hl + ld b, (hl) + ret + +; --------------------------------------------------------------------------- +; EMIT_CHAR — Escribe A en el buffer de salida y avanza el cursor +; --------------------------------------------------------------------------- +EMIT_CHAR: + push hl + ld hl, (FP_STR_WR) + ld (hl), a + inc hl + ld (FP_STR_WR), hl + pop hl + ret + +; --------------------------------------------------------------------------- +; EMIT_U32 — Escribe DEHL (entero sin signo de 32 bits) como digitos decimales +; en el buffer de salida (sin ceros a la izquierda; "0" si el valor es cero). +; Mismo algoritmo que __PRINTU32 (printi32.asm/printnum.asm), pero escribiendo +; en el buffer en vez de en pantalla. +; --------------------------------------------------------------------------- +EMIT_U32: + PROC + LOCAL EMIT_U32_LOOP + LOCAL EMIT_U32_START + LOCAL EMIT_U32_CONT + + ld b, 0 + +EMIT_U32_LOOP: + ld a, h + or l + or d + or e + jp z, EMIT_U32_START + + push bc + ld bc, 0 + push bc + ld bc, 10 + push bc + call __DIVU32 + pop bc + + exx + ld a, l + or '0' + push af + exx + inc b + jp EMIT_U32_LOOP + +EMIT_U32_START: + ld a, b + or a + jp nz, EMIT_U32_CONT + ld a, '0' + call EMIT_CHAR + ret + +EMIT_U32_CONT: + pop af + push bc + call EMIT_CHAR + pop bc + djnz EMIT_U32_CONT + ret + + ENDP + +; --------------------------------------------------------------------------- +; FP_TO_STR — Convierte un FLOAT a texto ASCII decimal +; Entrada: A,E,D,C,B = valor FLOAT (convención habitual del runtime) +; Salida: HL = puntero al texto (sin prefijo de longitud), BC = longitud +; --------------------------------------------------------------------------- +FP_TO_STR: + PROC + LOCAL FP_TO_STR_POS + LOCAL FP_TO_STR_POS2 + LOCAL FP_TO_STR_FRACLOOP + LOCAL FP_TO_STR_FRACDONE + LOCAL FP_TO_STR_TRIMLOOP + LOCAL FP_TO_STR_TRIMDOT + LOCAL FP_TO_STR_TRIMKEEP + LOCAL FP_TO_STR_DONE + + ld hl, FP_STR_ORIG + call STORE5 + + ld hl, FP_STR_BUF + ld (FP_STR_WR), hl + + ; ¿Es cero? (forma canonica: A=0 y mantisa completa a 0) + or e + or d + or c + or b + jr nz, FP_TO_STR_POS + ld a, '0' + call EMIT_CHAR + jp FP_TO_STR_DONE + +FP_TO_STR_POS: + ; El bit 7 de E es el signo tanto en formato entero-pequeno como en + ; coma flotante completa (ver fp_calc.asm / __FTOU32REG). + ld a, (FP_STR_ORIG + 1) + bit 7, a + jr z, FP_TO_STR_POS2 + + ld a, '-' + call EMIT_CHAR + + ; Vuelve positivo el valor original (negate) para trabajar siempre en abs + ld hl, FP_STR_ORIG + call LOAD5 + call __FPSTACK_PUSH + rst 28h + defb $1B ;;negate + defb $38 ;;end-calc + call __FPSTACK_POP + ld hl, FP_STR_ORIG + call STORE5 + +FP_TO_STR_POS2: + ; intx = INT(x) (x ya es >= 0 en este punto) + ld hl, FP_STR_ORIG + call LOAD5 + call __FPSTACK_PUSH + rst 28h + defb $31 ;;duplicate + defb $27 ;;int + defb $38 ;;end-calc + call __FPSTACK_POP + ld hl, FP_STR_INT + call STORE5 + + ; frac = x - intx + ld hl, FP_STR_INT + call LOAD5 + call __FPSTACK_PUSH + rst 28h + defb $03 ;;subtract + defb $38 ;;end-calc + call __FPSTACK_POP + ld hl, FP_STR_FRAC + call STORE5 + + ; imprime la parte entera (ya es >= 0, cabe en 32 bits sin signo) + ld hl, FP_STR_INT + call LOAD5 + call __FTOU32REG ; DEHL = parte entera + call EMIT_U32 + + ld a, '.' + call EMIT_CHAR + ld a, 5 + ld (FP_STR_COUNT), a + +FP_TO_STR_FRACLOOP: + ld hl, FP_STR_FRAC + call LOAD5 + call __FPSTACK_PUSH ; pila: [frac] + + xor a + ld d, 10 + ld e, a + ld c, a + ld b, a + call __FPSTACK_PUSH ; pila: [frac, 10] + rst 28h + defb $04 ;;multiply + defb $38 ;;end-calc + ; pila: [frac*10] + rst 28h + defb $31 ;;duplicate + defb $27 ;;int + defb $38 ;;end-calc + ; pila: [frac*10, digito] + call __FPSTACK_POP + call __FTOU8 ; A = digito (0-9) + push af ; guarda el digito (igual que val.asm con rst 28h) + + pop af + push af + ld d, a + xor a + ld e, a + ld c, a + ld b, a + call __FPSTACK_PUSH ; pila: [frac*10, digito] + rst 28h + defb $03 ;;subtract + defb $38 ;;end-calc + ; pila: [frac*10 - digito] = nuevo frac + call __FPSTACK_POP + ld hl, FP_STR_FRAC + call STORE5 + + pop af ; recupera el digito + or '0' + call EMIT_CHAR + + ld hl, FP_STR_COUNT + dec (hl) + jr nz, FP_TO_STR_FRACLOOP + +FP_TO_STR_FRACDONE: + ; recorta ceros finales (y el punto si todos los decimales eran cero) + ld hl, (FP_STR_WR) + +FP_TO_STR_TRIMLOOP: + dec hl + ld a, (hl) + cp '.' + jr z, FP_TO_STR_TRIMDOT + cp '0' + jr nz, FP_TO_STR_TRIMKEEP + jr FP_TO_STR_TRIMLOOP + +FP_TO_STR_TRIMDOT: + ld (FP_STR_WR), hl + jp FP_TO_STR_DONE + +FP_TO_STR_TRIMKEEP: + inc hl + ld (FP_STR_WR), hl + +FP_TO_STR_DONE: + ld hl, (FP_STR_WR) + ld de, FP_STR_BUF + or a + sbc hl, de ; HL = longitud del texto + ld b, h + ld c, l + ld hl, FP_STR_BUF + ret + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/pixel_addr.asm b/src/lib/arch/zx81sd/runtime/pixel_addr.asm index 524a0c129..93a0156f8 100644 --- a/src/lib/arch/zx81sd/runtime/pixel_addr.asm +++ b/src/lib/arch/zx81sd/runtime/pixel_addr.asm @@ -5,7 +5,9 @@ ; Entrada: A = 191 (límite superior Y), B = Y (0=abajo, 191=arriba), C = X (0-255) ; Salida: HL = offset dentro del bitmap desde $0000 (sin base de pantalla) ; A = X AND 7 (posición del bit, 0=izquierda/bit7, 7=derecha/bit0) -; Destruye: B, D +; Destruye: B (como la ROM). DEBE preservar D y E: draw.asm salva B' en D' +; alrededor de esta llamada (ld d,b / call PIXEL_ADDR / ld b,d), igual que +; hacía con PIXEL-ADD ($22AC) de la ROM Spectrum, que tampoco tocaba DE. ; ; El llamador (plot.asm, draw.asm) añade la base de pantalla: ; res 6, h ; no-op en nuestro caso (H siempre en $00-$17) @@ -22,7 +24,7 @@ PIXEL_ADDR: PROC sub b ; A = 191 - Y (convierte coord Spectrum a offset desde arriba) - ld d, a ; D = V = 191-Y + ld b, a ; B = V = 191-Y (B ya no se necesita: era la Y de entrada) ; -- H: tercio (bits 12-11) + línea en tercio (bits 10-8) -- and $C0 ; A = (V AND $C0) = tercio * 64 @@ -30,22 +32,16 @@ PIXEL_ADDR: rrca rrca ; A = tercio * 8 ld h, a - ld a, d + ld a, b and $07 ; A = línea en tercio (0-7) or h ld h, a ; H = (tercio<<3) | línea_en_tercio ; -- L: fila de carácter (bits 7-5) + columna de byte (bits 4-0) -- - ld a, d + ld a, b and $38 ; A = fila_de_char * 8 - rrca - rrca - rrca ; A = fila_de_char (0-7) - rlca - rlca - rlca rlca - rlca ; A = fila_de_char * 32 + rlca ; A = fila_de_char * 32 (tras AND $38 no hay acarreo al rotar) ld b, a ld a, c rrca diff --git a/src/lib/arch/zx81sd/runtime/printf.asm b/src/lib/arch/zx81sd/runtime/printf.asm new file mode 100644 index 000000000..5c88cbb48 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/printf.asm @@ -0,0 +1,37 @@ +; printf.asm (zx81sd) — PRINT de un numero FLOAT +; +; Sustituye a zx48k/runtime/printf.asm, que usa el literal 'str$' ($2Eh) del +; calculador de la ROM (STR$ + STK-STO-$ + heap temporal) para despues +; imprimir la cadena resultante. Aqui se usa directamente fp_tostr.asm +; (conversion simplificada ya portada) e imprime sus caracteres uno a uno, +; sin pasar por el heap. + +#include once +#include once + + push namespace core + +__PRINTF: + ; Entrada: A,E,D,C,B = valor FLOAT + call FP_TO_STR ; HL = puntero al texto, BC = longitud + PROC + LOCAL __PRINTF_LOOP + +__PRINTF_LOOP: + ld a, b + or c + ret z + + ld a, (hl) + push hl + push bc + call __PRINTCHAR + pop bc + pop hl + inc hl + dec bc + jr __PRINTF_LOOP + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/stackf.asm b/src/lib/arch/zx81sd/runtime/stackf.asm new file mode 100644 index 000000000..9473ddda2 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/stackf.asm @@ -0,0 +1,97 @@ +; stackf.asm (zx81sd) — Gestión de la pila del calculador FP +; +; Sustituye a zx48k/runtime/stackf.asm, que define __FPSTACK_PUSH/POP como +; direcciones FIJAS de la ROM del Spectrum ($2AB6h STK-STORE, $2BF1h +; STK-FETCH). En zx81sd esas direcciones son parte de nuestro propio +; binario compilado (varían de un programa a otro), así que no se pueden +; usar como constantes — hay que reimplementar ambas rutinas como código +; reubicable normal, usando fp_calc.asm (mismo formato de pila y de número +; de 5 bytes que el motor CALCULATE ya portado). + +#include once + + push namespace core + +; --------------------------------------------------------------------------- +; __FPSTACK_PUSH — Apila los registros A,E,D,C,B (5 bytes) en la pila FP +; Sustituye a STK-STORE ($2AB6h ROM Spectrum) +; --------------------------------------------------------------------------- +__FPSTACK_PUSH: + push bc + push af + ld bc, 5 + call CALC_TEST_ROOM + pop af + pop bc + ld hl, (FP_STKEND) + ld (hl), a + inc hl + ld (hl), e + inc hl + ld (hl), d + inc hl + ld (hl), c + inc hl + ld (hl), b + inc hl + ld (FP_STKEND), hl + ret + +__FPSTACK_PUSH2: ; Pushes Current A ED CB registers and top of the stack on (SP + 4) + ; Second argument to push into the stack calculator is popped out of the stack + ; Since the caller routine also receives the parameters into the top of the stack + ; four bytes must be removed from SP before pop them out + + call __FPSTACK_PUSH ; Pushes A ED CB into the FP-STACK + exx + pop hl ; Caller-Caller return addr + exx + pop hl ; Caller return addr + + pop af + pop de + pop bc + + push hl ; Caller return addr + exx + push hl ; Caller-Caller return addr + exx + + jp __FPSTACK_PUSH + + +__FPSTACK_I16: ; Pushes 16 bits integer in HL into the FP ROM STACK + ; This format is specified in the ZX 48K Manual + ; You can push a 16 bit signed integer as + ; 0 SS LL HH 0, being SS the sign and LL HH the low + ; and High byte respectively + ld a, h + rla ; sign to Carry + sbc a, a ; 0 if positive, FF if negative + ld e, a + ld d, l + ld c, h + xor a + ld b, a + jp __FPSTACK_PUSH + +; --------------------------------------------------------------------------- +; __FPSTACK_POP — Extrae los últimos 5 bytes de la pila FP a A,E,D,C,B +; Sustituye a STK-FETCH ($2BF1h ROM Spectrum) +; --------------------------------------------------------------------------- +__FPSTACK_POP: + ld hl, (FP_STKEND) + dec hl + ld b, (hl) + dec hl + ld c, (hl) + dec hl + ld d, (hl) + dec hl + ld e, (hl) + dec hl + ld a, (hl) + ld (FP_STKEND), hl + ret + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/str.asm b/src/lib/arch/zx81sd/runtime/str.asm new file mode 100644 index 000000000..e603f8dd5 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/str.asm @@ -0,0 +1,54 @@ +; str.asm (zx81sd) — La funcion STR$( ) +; +; Sustituye a zx48k/runtime/str.asm, que usa el literal 'str$' ($2Eh) del +; calculador de la ROM junto con STK-STO-$ y RECLAIM2 (ROM $19E8h) para +; construir la cadena en el area de trabajo de la ROM. Aqui se usa +; directamente fp_tostr.asm (conversion simplificada ya portada) y se copia +; el resultado a un bloque nuevo del heap propio (mem/alloc.asm). + +#include once +#include once + + push namespace core + +__STR: +__STR_FAST: + ; Entrada: A,E,D,C,B = valor FLOAT + ; Salida: HL = puntero a la cadena (heap), formato [longitud(2B)][texto] + call FP_TO_STR ; HL = puntero al texto, BC = longitud + PROC + LOCAL __STR_END + + push hl ; guarda puntero al texto (FP_STR_BUF) + push bc ; guarda longitud + + ld hl, 2 + add hl, bc + ld b, h + ld c, l + call __MEM_ALLOC ; HL = nuevo bloque de (longitud+2) bytes (o NULL) + + pop bc ; longitud del texto + pop de ; puntero al texto (FP_STR_BUF) + + ld a, h + or l + jr z, __STR_END ; sin memoria -> devuelve NULL + + push hl + ld (hl), c + inc hl + ld (hl), b + inc hl ; HL = destino del texto + + ex de, hl ; HL = origen (texto), DE = destino + ldir + + pop hl ; HL = puntero a la cadena resultante + +__STR_END: + ret + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/sysvars.asm b/src/lib/arch/zx81sd/runtime/sysvars.asm index 404b35eb7..ec0a4c01a 100644 --- a/src/lib/arch/zx81sd/runtime/sysvars.asm +++ b/src/lib/arch/zx81sd/runtime/sysvars.asm @@ -48,7 +48,25 @@ RANDOM_SEED_LOW EQU SYSVAR_BASE + $1C ; DW — semilla RNG (16 bits bajos SCREEN_ADDR EQU SYSVAR_BASE + $1E ; DW — puntero al framebuffer (init: $C000) SCREEN_ATTR_ADDR EQU SYSVAR_BASE + $20 ; DW — puntero a atributos (init: $D800) -; Tamaño total del bloque de sysvars: $22 bytes +; --- Sysvars del calculador de coma flotante (fp_calc.asm) -------------- +; Equivalentes a STKBOT/STKEND/BREG/MEM de la ROM Spectrum ($5C63/$5C65/ +; $5C67/$5C68), pero apuntando a un buffer fijo propio en vez de al área +; de trabajo dinámica de la ROM (aquí no existe "memoria libre creciente" +; entre el programa y la pila de máquina). +; +; IMPORTANTE: FP_BREG debe estar INMEDIATAMENTE DESPUÉS de FP_STKEND — el +; motor CALCULATE (L338E, ENT-TABLE) explota la contigüidad de memoria de +; la ROM original (STKEND_hi seguido de BREG) para cargar ambos con un +; único LD BC,(FP_STKEND+1): C=STKEND_hi, B=BREG. No reordenar. +FP_STKBOT EQU SYSVAR_BASE + $22 ; DW — base de la pila de números FP +FP_STKEND EQU SYSVAR_BASE + $24 ; DW — siguiente posición libre en la pila FP +FP_BREG EQU SYSVAR_BASE + $26 ; DB — literal en curso (para fp-calc-2/dec-jr-nz) +FP_MEM EQU SYSVAR_BASE + $27 ; DW — puntero al área MEM (6 celdas de 5B) +FP_CALC_STACK EQU SYSVAR_BASE + $29 ; 60B — pila de números FP (12 números máx.) +FP_CALC_STACK_END EQU FP_CALC_STACK + 60 +FP_MEM_AREA EQU SYSVAR_BASE + $65 ; 30B — área MEM (6 celdas de 5 bytes) + +; Tamaño total del bloque de sysvars: $83 bytes ; --- Constantes de pantalla --------------------------------------------- diff --git a/src/lib/arch/zx81sd/runtime/val.asm b/src/lib/arch/zx81sd/runtime/val.asm new file mode 100644 index 000000000..df642dbb0 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/val.asm @@ -0,0 +1,210 @@ +; val.asm (zx81sd) — VAL(a$): convierte texto a numero en coma flotante +; +; Sustituye a zx48k/runtime/val.asm, que usa VAL de la ROM del Spectrum: +; ademas de convertir el texto a numero, la ROM real vuelve a meter la +; cadena en el interprete de BASIC y la evalua como una expresion completa +; (por eso en un Spectrum real VAL("2+2") funciona y da 4). Esa parte vive +; en el escaner de lineas de BASIC de la ROM, un subsistema aparte del +; calculador (no portado aqui). +; +; Esta version soporta solo un LITERAL DECIMAL simple: signo opcional, +; digitos, y un punto decimal opcional seguido de mas digitos. NO evalua +; expresiones (VAL("2+2") no funciona; VAL("2.5") o VAL("-13") si). Es el +; uso habitual de VAL(INPUT(...)) para leer numeros tecleados por el +; usuario. Cualquier caracter no numerico corta el parseo en ese punto +; (el resto de la cadena se ignora), en vez de dar un error. +; +; El numero se construye acumulando digito a digito con el propio +; calculador ya portado (fp_calc.asm): valor = valor*10 + digito, y al +; final se divide entre 10^(numero de decimales) si hubo parte fraccionaria. + +#include once +#include once + + push namespace core + +; --- Variables de trabajo (no reentrante, uso transitorio durante VAL) ----- +VAL_PTR: defw 0 ; puntero al siguiente caracter a leer +VAL_LEN: defw 0 ; caracteres restantes por leer +VAL_STRPTR: defw 0 ; puntero original a la cadena (para liberarla) +VAL_FREE_FLAG: defb 0 ; 1 si hay que liberar la cadena al terminar +VAL_NEG: defb 0 ; 1 si el numero es negativo +VAL_INFRAC: defb 0 ; 1 si ya se paso el punto decimal +VAL_DECIMALS: defb 0 ; numero de digitos leidos tras el punto decimal + +VAL: + ; Entrada: HL = direccion de a$ (2 bytes de longitud + datos) + ; A = 1 si hay que liberar a$ al terminar (no es variable) + ; Salida: A EDCB = numero en punto flotante (via __FPSTACK_POP) + PROC + + LOCAL VAL_EMPTY + LOCAL VAL_LOOP + LOCAL VAL_GOT_SIGN + LOCAL VAL_NOT_DOT + LOCAL VAL_DIGIT + LOCAL VAL_DIGIT_ADVANCE + LOCAL VAL_DONE + LOCAL VAL_DIV_LOOP + LOCAL VAL_NOT_NEG + LOCAL VAL_EMPTY_SKIP + LOCAL VAL_NO_FREE + LOCAL PUSH_DIGIT + + ld (VAL_FREE_FLAG), a + ld a, h + or l + jp z, VAL_EMPTY ; cadena NULL -> 0 (jp: VAL_EMPTY queda lejos) + + ld (VAL_STRPTR), hl + + ld e, (hl) + inc hl + ld d, (hl) + inc hl ; DE = longitud de la cadena + ld (VAL_LEN), de + ld (VAL_PTR), hl ; HL = inicio del texto + + xor a + ld (VAL_NEG), a + ld (VAL_INFRAC), a + ld (VAL_DECIMALS), a + + ld hl, (VAL_LEN) + ld a, h + or l + jp z, VAL_DONE ; cadena vacia -> 0 + + ld hl, (VAL_PTR) + ld a, (hl) + cp '-' + jr nz, VAL_GOT_SIGN + ld a, 1 + ld (VAL_NEG), a + inc hl + ld (VAL_PTR), hl + ld de, (VAL_LEN) + dec de + ld (VAL_LEN), de +VAL_GOT_SIGN: + + ; acumulador FP = 0 + xor a + ld e, a + ld d, a + ld c, a + ld b, a + call __FPSTACK_PUSH + +VAL_LOOP: + ld hl, (VAL_LEN) + ld a, h + or l + jp z, VAL_DONE + + ld hl, (VAL_PTR) + ld a, (hl) + + cp '.' + jr nz, VAL_NOT_DOT + ld a, 1 + ld (VAL_INFRAC), a + jr VAL_DIGIT_ADVANCE ; el punto no cuenta como digito, solo avanza + +VAL_NOT_DOT: + cp '0' + jp c, VAL_DONE + cp '9' + 1 + jp nc, VAL_DONE + +VAL_DIGIT: + sub '0' ; A = digito 0-9 + push af + + ld a, 10 + call PUSH_DIGIT + rst 28h + defb $04 ;;multiply + defb $38 ;;end-calc + + pop af + call PUSH_DIGIT + rst 28h + defb $0F ;;addition + defb $38 ;;end-calc + + ld a, (VAL_INFRAC) + or a + jr z, VAL_DIGIT_ADVANCE + ld hl, VAL_DECIMALS + inc (hl) + +VAL_DIGIT_ADVANCE: + ld hl, (VAL_PTR) + inc hl + ld (VAL_PTR), hl + ld hl, (VAL_LEN) + dec hl + ld (VAL_LEN), hl + jp VAL_LOOP + +VAL_DONE: + ld a, (VAL_DECIMALS) + or a + jr z, VAL_NOT_NEG + ld b, a +VAL_DIV_LOOP: + push bc + ld a, 10 + call PUSH_DIGIT + rst 28h + defb $05 ;;division + defb $38 ;;end-calc + pop bc + djnz VAL_DIV_LOOP + +VAL_NOT_NEG: + ld a, (VAL_NEG) + or a + jr z, VAL_EMPTY_SKIP + rst 28h + defb $1B ;;negate + defb $38 ;;end-calc +VAL_EMPTY_SKIP: + + call __FPSTACK_POP ; A EDCB = resultado + + push af + push de + push bc + ld a, (VAL_FREE_FLAG) + or a + jr z, VAL_NO_FREE + ld hl, (VAL_STRPTR) + call __MEM_FREE +VAL_NO_FREE: + pop bc + pop de + pop af + ret + +VAL_EMPTY: + xor a + ld e, a + ld d, a + ld c, a + ld b, a + jp __FPSTACK_POP + +; --- Apila A (0-255) como entero pequeño positivo --------------------------- +PUSH_DIGIT: + ld d, a + xor a + ld e, a + ld c, a + ld b, a + jp __FPSTACK_PUSH + + ENDP + + pop namespace From 7ae4d10a2b8747c930fd70312b599a1d8991fcb1 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Thu, 2 Jul 2026 16:47:41 +0200 Subject: [PATCH 05/27] zx81sd: BEEP nativo y PLAY sobre los AY ZonX del SD81 Booster BEEP (sin ROM Spectrum): - io/sound/beeper.asm: bucle BEEPER de la ROM ($03B5) portado al puerto ULA $FB del SD81 (bits como el $FE del Spectrum), con el borde tomado de la copia sombra y sin EI final (el runtime corre con las interrupciones deshabilitadas). La entrada __BEEPER del compilador corrige el periodo precalculado por el frontend (que usa la constante del Spectrum, 437500 = 3.5MHz/8) a los 3.25 MHz del ZX81: HL' = HL*13/14 - 2 (406250/437500 = 13/14 exacto). - io/sound/beep.asm: comando BEEP de la ROM ($03F8) portado sobre el calculador FP propio: misma matematica (incluida la parte fraccionaria del semitono), tabla de semitonos de la ROM, y constante 406250 (= 3.25MHz/8). FIND-INT1/2 sustituidos por __FPSTACK_POP+__FTOU32REG. PLAY (chip A del ZonX, 3 canales): - stdlib/play.bas: copia de zx48k/stdlib/play.bas con 4 cambios documentados en cabecera: puertos ZonX (latch $CF, dato $0F), tabla de divisores regenerada para reloj AY de 1.625 MHz (26MHz/16 en la FPGA), CpuCyclesPerSecond = 3250000 para el tempo, y sin EI final. Verificado en EightyOne con analisis FFT de la salida de audio: beeper y AY al unisono en 440 Hz (LA4), escala y acorde de 3 canales correctos, duraciones exactas. Co-Authored-By: Claude Fable 5 --- src/lib/arch/zx81sd/runtime/io/sound/beep.asm | 208 +++++++ .../arch/zx81sd/runtime/io/sound/beeper.asm | 142 +++++ src/lib/arch/zx81sd/stdlib/play.bas | 510 ++++++++++++++++++ 3 files changed, 860 insertions(+) create mode 100644 src/lib/arch/zx81sd/runtime/io/sound/beep.asm create mode 100644 src/lib/arch/zx81sd/runtime/io/sound/beeper.asm create mode 100644 src/lib/arch/zx81sd/stdlib/play.bas diff --git a/src/lib/arch/zx81sd/runtime/io/sound/beep.asm b/src/lib/arch/zx81sd/runtime/io/sound/beep.asm new file mode 100644 index 000000000..f2562fcee --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/io/sound/beep.asm @@ -0,0 +1,208 @@ +; beep.asm (zx81sd) — Comando BEEP duracion, tono (version con expresiones) +; +; Sustituye a zx48k/runtime/io/sound/beep.asm, que llama a BEEP ($03F8) de +; la ROM Spectrum. Aqui se porta la rutina original usando el calculador FP +; propio (fp_calc.asm) y el bucle de beeper propio (io/sound/beeper.asm). +; +; BEEP dur, pitch +; dur = duracion en segundos (0 a 10) +; pitch = semitonos sobre/bajo el DO central (-60 a 127) +; +; Diferencias con la ROM: +; - MEM-0 esta en FP_MEM_AREA (sysvars propias), no en $5C92. +; - FIND-INT1/FIND-INT2 se sustituyen por __FPSTACK_POP + __FTOU32REG +; (ya portados y verificados en zx81sd). +; - La constante 437500 (= 3.5MHz/8 del Spectrum) se sustituye por +; 406250 (= 3.25MHz/8 del ZX81). El resto del bytecode es identico. +; +; Entrada (convencion del compilador, igual que en zx48k): +; Duracion en A,E,D,C,B (float); tono en la pila de maquina (float). + +#include once +#include once +#include once +#include once +#include once + + push namespace core + +BEEP: + PROC + LOCAL BEEP_I_OK, BEEP_OCTAVE, BEEP_ERROR, BEEP_SEMITONES + + call __FPSTACK_PUSH ; duracion -> pila FP + + pop hl ; direccion de retorno + pop af + pop de + pop bc ; tono (float) desde la pila de maquina + push hl ; CALLEE + + call __FPSTACK_PUSH ; tono -> pila FP + +; Igual que la ROM ($03F8): separa el tono en parte entera (mem-0) y +; fraccionaria, y deja en la pila 1 + 0.05776226 * frac(tono). + + rst 28h ;; FP-CALC dur, tono. + defb $31 ;;duplicate dur, tono, tono. + defb $27 ;;int dur, tono, int(tono). + defb $C0 ;;st-mem-0 (tono entero a mem-0) + defb $03 ;;subtract dur, frac(tono). + defb $34 ;;stk-data constante 0.05776226 + defb $EC ;;Exponent: $7C, Bytes: 4 + defb $6C, $98, $1F, $F5 + defb $04 ;;multiply + defb $A1 ;;stk-one + defb $0F ;;addition dur, 1 + 0.0577*frac. + defb $38 ;;end-calc + +; mem-0 contiene el tono entero en formato entero-pequeno: +; 0, signo (0/FF), LSB, MSB, 0. Comprueba -128 <= tono <= 127. + + ld hl, FP_MEM_AREA + ld a, (hl) ; el primer byte debe ser 0 (forma entera) + and a + jp nz, BEEP_ERROR + + inc hl + ld c, (hl) ; C = byte de signo (0/FF) + inc hl + ld b, (hl) ; B = LSB (complemento a dos) + ld a, b + rla + sbc a, a ; A = 0/FF segun el bit 7 de B + cp c ; debe coincidir con el signo + jp nz, BEEP_ERROR + + inc hl + cp (hl) ; y el MSB debe ser 0/FF igualmente + jp nz, BEEP_ERROR + + ld a, b ; A = tono + 60 + add a, $3C + jp p, BEEP_I_OK ; si -60 <= tono, sigue + + jp po, BEEP_ERROR ; fuera de rango por abajo + +BEEP_I_OK: ; aqui -60 <= tono <= 127, A = tono+60 (0-187) + ld b, $FA ; B = -6 octavas bajo el DO central + +BEEP_OCTAVE: + inc b ; octava siguiente + sub $0C ; 12 semitonos = 1 octava + jr nc, BEEP_OCTAVE + + add a, $0C ; A = semitonos sobre DO (0-11) + push bc ; B = desplazamiento de octava (-5..10) + + ; HL = BEEP_SEMITONES + A*5 (LOC-MEM de la ROM) + ld c, a + add a, a + add a, a + add a, c ; A = A*5 (max 55, sin acarreo) + ld c, a + ld b, 0 + ld hl, BEEP_SEMITONES + add hl, bc + + ; STACK-NUM: apila el float de la tabla (frecuencia del semitono) + ld a, (hl) + inc hl + ld e, (hl) + inc hl + ld d, (hl) + inc hl + ld c, (hl) + inc hl + ld b, (hl) + call __FPSTACK_PUSH + + rst 28h ;; FP-CALC dur, factor, freq. + defb $04 ;;multiply dur, freq ajustada a frac(tono). + defb $38 ;;end-calc (HL -> exponente del resultado) + + pop af ; A = desplazamiento de octava + add a, (hl) ; freq *= 2^octava (suma al exponente) + ld (hl), a + + rst 28h ;; FP-CALC dur, freq. + defb $C0 ;;st-mem-0 (frecuencia a mem-0) + defb $02 ;;delete dur. + defb $31 ;;duplicate dur, dur. + defb $38 ;;end-calc + + ; comprueba 0 <= duracion <= 10 (como FIND-INT1 + CP 11 de la ROM) + call __FPSTACK_POP + bit 7, e ; negativa -> error + jp nz, BEEP_ERROR + call __FTOU32REG ; DEHL = int(duracion) + ld a, d + or e + or h + jp nz, BEEP_ERROR + ld a, l + cp $0B + jp nc, BEEP_ERROR + +; Calcula los parametros del bucle del beeper: +; ciclos = duracion * frecuencia +; periodo = 406250 / frecuencia - 30.125 (406250 = 3.25MHz / 8) + + rst 28h ;; FP-CALC dur. + defb $E0 ;;get-mem-0 dur, freq. + defb $04 ;;multiply ciclos. + defb $E0 ;;get-mem-0 ciclos, freq. + defb $34 ;;stk-data constante 406250 + defb $80 ;;Exponent: $93, Bytes: 3 + defb $43 + defb $46, $5D, $40 + defb $01 ;;exchange ciclos, 406250, freq. + defb $05 ;;division ciclos, 406250/freq. + defb $34 ;;stk-data constante 30.125 + defb $35 ;;Exponent: $85, Bytes: 1 + defb $71 + defb $03 ;;subtract ciclos, periodo. + defb $38 ;;end-calc + + call __FPSTACK_POP + call __FTOU32REG ; HL = periodo + push hl + call __FPSTACK_POP + call __FTOU32REG ; HL = ciclos + ex de, hl ; DE = ciclos + pop hl ; HL = periodo + + ld a, d + or e + ret z ; duracion 0: nada que hacer (evita 65536 ciclos) + dec de ; DE = ciclos - 1 + + push ix ; el beeper usa IX (frame pointer del compilador) + call __ZX81SD_BEEPER + pop ix + ret + +BEEP_ERROR: + ld a, ERROR_IntOutOfRange + jp __ERROR + +; Tabla de semitonos de la ROM ($046E): frecuencias de la octava central. +; Octavas arriba/abajo = multiplicar por 2^n (se suma n al exponente). + +BEEP_SEMITONES: + defb $89, $02, $D0, $12, $86 ; 261.625565290 DO + defb $89, $0A, $97, $60, $75 ; 277.182631135 DO# + defb $89, $12, $D5, $17, $1F ; 293.664768100 RE + defb $89, $1B, $90, $41, $02 ; 311.126983881 RE# + defb $89, $24, $D0, $53, $CA ; 329.627557039 MI + defb $89, $2E, $9D, $36, $B1 ; 349.228231549 FA + defb $89, $38, $FF, $49, $3E ; 369.994422674 FA# + defb $89, $43, $FF, $6A, $73 ; 391.995436072 SOL + defb $89, $4F, $A7, $00, $54 ; 415.304697513 SOL# + defb $89, $5C, $00, $00, $00 ; 440.000000000 LA + defb $89, $69, $14, $F6, $24 ; 466.163761616 LA# + defb $89, $76, $F1, $10, $05 ; 493.883301378 SI + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/io/sound/beeper.asm b/src/lib/arch/zx81sd/runtime/io/sound/beeper.asm new file mode 100644 index 000000000..1f99f7c98 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/io/sound/beeper.asm @@ -0,0 +1,142 @@ +; beeper.asm (zx81sd) — Generador de onda cuadrada por el beeper del SD81 +; +; Sustituye a zx48k/runtime/io/sound/beeper.asm, que llama a BEEPER ($03B5) +; de la ROM Spectrum. Aqui se porta el bucle original de la ROM adaptando: +; +; - Puerto: $FB (ULA emulada del SD81 en modo HiRes Spectrum) en vez de +; $FE. Mismo formato: bits 2-0 = borde, bit 3 = MIC, bit 4 = altavoz. +; El borde se toma de la copia sombra __ZX81SD_ULA_SHADOW (beep.asm) +; en vez de BORDCR ($5C48), y se restaura al terminar. +; - Reloj: el bucle es identico en T-states (no depende del reloj), pero +; el periodo HL que llega del COMPILADOR (BEEP const,const se precalcula +; en el frontend con la formula del Spectrum: HL = 437500/f - 30.125, +; 437500 = 3.5MHz/8) hay que corregirlo para los 3.25 MHz del ZX81: +; HL' = 406250/f - 30.125 = HL*13/14 - 2.15 (406250/437500 = 13/14) +; La entrada __BEEPER (la que usa el compilador) aplica esa correccion. +; La entrada __ZX81SD_BEEPER (interna) recibe el periodo ya en unidades +; de 3.25 MHz y no corrige nada (la usa nuestro BEEP de beep.asm, que +; calcula directamente con 406250). +; - Interrupciones: la ROM hace DI...EI. El runtime zx81sd se ejecuta con +; las interrupciones deshabilitadas (la FPGA genera el video), asi que +; se mantiene el DI (inocuo) y NO se hace EI al final. +; +; __ZX81SD_BEEPER — entrada directa (formato ROM $03B5): +; DE = numero de ciclos - 1 +; HL = periodo del tono: T-states = 236 + 8*HL (a 3.25 MHz) +; Modifica: AF, BC, DE, HL, IX (el llamador debe preservar IX si lo usa) + +#include once ; __ZX81SD_ULA_SHADOW (sombra borde/beeper) + + push namespace core + +__ZX81SD_BEEPER: + PROC + LOCAL BE_IX3, BE_IX2, BE_IX1, BE_IX0 + LOCAL BE_HL_LP, BE_AGAIN, BE_END + + di ; timing exacto (ya suelen estar deshabilitadas) + ld a, l + srl l + srl l ; L = parte media del periodo + cpl + and $03 ; A = 3 - parte fina del periodo + ld c, a + ld b, $00 + ld ix, BE_IX3 + add ix, bc ; IX = entrada al bucle con 0-3 NOPs (parte fina) + + ld a, (__ZX81SD_ULA_SHADOW) + and $07 ; bits 2-0 = borde actual + or $08 ; bit 3 (MIC) a 1, como la ROM + +BE_IX3: + nop ;(4) NOPs opcionales: ajuste fino del periodo +BE_IX2: + nop ;(4) +BE_IX1: + nop ;(4) +BE_IX0: + inc b ;(4) + inc c ;(4) + +BE_HL_LP: + dec c ;(4) bucle de duracion del semiciclo + jr nz, BE_HL_LP ;(12/7) + + ld c, $3F ;(7) + dec b ;(4) + jp nz, BE_HL_LP ;(10) + + xor $10 ;(7) conmuta el bit del altavoz + out (SD81_ULA_PORT), a ;(11) + ld b, h ;(4) B = parte gruesa del periodo + ld c, a ;(4) salva el byte del puerto + bit 4, a ;(8) si la salida quedo alta, + jr nz, BE_AGAIN ;(12/7) hace el semiciclo alto + + ld a, d ;(4) ciclo completo (bajo->bajo): + or e ;(4) ¿quedan ciclos? + jr z, BE_END ;(12/7) + + ld a, c ;(4) restaura el byte del puerto + ld c, l ;(4) C = parte media del periodo + dec de ;(6) + jp (ix) ;(8) siguiente ciclo + +BE_AGAIN: ; a mitad de ciclo + ld c, l ;(4) + inc c ;(4) +16 T para igualar semiciclo alto y bajo + jp (ix) ;(8) + +BE_END: + ld a, (__ZX81SD_ULA_SHADOW) + out (SD81_ULA_PORT), a ; deja borde/beeper como estaban (sin EI: ver cabecera) + ret + + ENDP + +; --------------------------------------------------------------------------- +; __BEEPER — Entrada del compilador para BEEP , +; HL (fastcall) = numero de ciclos - 1 +; (SP+2) en la pila = periodo calculado por el frontend PARA 3.5 MHz +; Corrige el periodo a 3.25 MHz (HL' = HL - HL/14 - 2) y llama al bucle. +; --------------------------------------------------------------------------- +__BEEPER: + PROC + LOCAL DIV14, DIV14_SKIP + + ex de, hl ; DE = ciclos - 1 + pop hl ; direccion de retorno + ex (sp), hl ; HL = periodo (unidades Spectrum) — CALLEE + + push de ; salva ciclos + push hl ; salva periodo original + ex de, hl ; DE = dividendo (periodo) + xor a ; A = resto + ld c, 14 + ld b, 16 +DIV14: + sla e ; desplaza el dividendo (bit 0 entra a 0; + rl d ; NO usar rl e: arrastraria el acarreo residual + rla ; del cp/sub de la vuelta anterior) + cp c + jr c, DIV14_SKIP + sub c + inc e +DIV14_SKIP: + djnz DIV14 ; DE = periodo / 14 + pop hl ; periodo original + or a + sbc hl, de ; HL = periodo - periodo/14 (= periodo*13/14) + dec hl + dec hl ; -2 (~ -30.125*(1-13/14), redondeado) + pop de ; ciclos - 1 + + push ix ; el bucle usa IX; el compilador usa IX como frame + call __ZX81SD_BEEPER + pop ix + ret + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/stdlib/play.bas b/src/lib/arch/zx81sd/stdlib/play.bas new file mode 100644 index 000000000..84eab2546 --- /dev/null +++ b/src/lib/arch/zx81sd/stdlib/play.bas @@ -0,0 +1,510 @@ +' ---------------------------------------------------------------------- +' play.bas (zx81sd) — COPIA de zx48k/stdlib/play.bas adaptada al hardware +' AY del SD81 Booster (interfaz ZonX-81). Mantener sincronizada con el +' original de zx48k; los UNICOS cambios respecto a el son: +' +' 1. _PLAY_WRITE_TO_REGISTER: puertos ZonX del chip A (latch $CF, +' dato $0F) en vez de los del Spectrum 128 ($FFFD/$BFFD). +' 2. _Play_NoteDividers: tabla regenerada para reloj AY de 1.625 MHz +' (la FPGA del SD81 clockea los AY a 3.25MHz/2) en vez de los +' 1.7734 MHz del Spectrum 128. divisor = round(1625000/16/f). +' 3. CpuCyclesPerSecond: 3250000 (ZX81) en vez de 3546900 (128K), +' para que el tempo sea correcto. +' 4. Sin EI al terminar: el runtime zx81sd funciona con las +' interrupciones deshabilitadas (la FPGA genera el video). +' ---------------------------------------------------------------------- + +' ---------------------------------------------------------------------- +' This file is released under the MIT License +' +' Copyright (C) 2026 +' by Oleg S. Kostenko (a.k.a. Ollibony) +' ---------------------------------------------------------------------- + +#pragma once + +#pragma push(explicit) +#pragma push(strict) + +#pragma explicit = true +#pragma strict = true + +' --------------------------------------------------------------------------------------------------------------------- +' Plays the given MML strings on the AY music chip. +' The syntax is compatible with the Sinclair Basic Play routine. +' The documentation can be found here: https://fizyka.umk.pl/~jacek/zx/doc/man128/sp128p09.html +' +' This is work in progress. + +' The following commands are already implemented: +' - cdefgab, CDEFGAB - gives pitch of note within current octave range +' - $ - flattens note following it +' - # - sharpens note following it +' - & - denotes a rest +' - 1-12 - sets length of notes +' - _ - creates tied notes (sums multiple durations) +' - O - followed by a number 0 to 8 sets current octave range +' - V - followed by a number 0 to 15 sets volume of notes +' - T - followed by a number 60 to 240 sets tempo of music +' - N - separates two numbers (actually, any unexpected character does this, including space) +' +' The following commands are not implemented yet: +' - W, U, X - set volume effects +' - () - repetition +' - !! - comments +' - H - stop +' - M - channel mixer control +' - Y, Z - MIDI control (also you can't now pass more than 3 parameters to Play). +' +' Notes: +' - Unlike Sinclair Basic Play routine, this one doesn't insert tiny pauses between adjacent notes. +' I consider this to be a feature, rather than a bug. +' - There are no checks for incorrect commands or parameters. Unknown commands are silently ignored, +' and incorrect parameters cause undefined behavior. +' - This routine is more flexible in the way it parses commands than Sinclair Basic Play routine. +' Some combinations that give errors in Sinclair Basic, will play fine in this implementation. +' - This sub tends to provide more accurate timings than the original Sinclair Basic Play routine. +' However, perfect timing is not guaranteed, it may fluctuate depending on the complexity of the melody. +' - There can be subtle difference in behaviour between this sub and Sinclair Play, +' especially in undocumented edge cases (such as using ties together with triplets). +' - This sub disables interrupts at the start, and enables them in the end, +' regardless of whether they were enabled or not before. +' - The strings are passed by value and thus are copied on the routine invocation. +' The memory-effective version of this routine is yet to be implemented. +' - The compiler gives warning `[W150] Parameter 'microticks' is never used`. +' This is false positive and, unfortunately, cannot be suppressed on library level. +' --------------------------------------------------------------------------------------------------------------------- +declare sub Play(channel0 as string, channel1 as string = "", channel2 as string = "") + + +' Implementation ------------------------------------------------------------------------------------------------------ + +' How many ticks there are in a bar (a whole note). +' A tick is a single iteration of the main processing loop. +const _Play_TicksPerBar as ubyte = 96 + +const _Play_NotesPerOctave as ubyte = 12 +const _Play_TotalOctaves as ubyte = 9 + +' Maps note length to the corresponding number of ticks. +dim _Play_NoteLengthsInTicks(1 to 12) as ubyte => { _ + _Play_TicksPerBar / 16, _ '1 - semi-quaver + _Play_TicksPerBar / 16 * 1.5, _ '2 - dotted semi-quaver + _Play_TicksPerBar / 8, _ '3 - quaver + _Play_TicksPerBar / 8 * 1.5, _ '4 - dotted quaver + _Play_TicksPerBar / 4, _ '5 - crotchet + _Play_TicksPerBar / 4 * 1.5, _ '6 - dotted crotchet + _Play_TicksPerBar / 2, _ '7 - minim + _Play_TicksPerBar / 2 * 1.5, _ '8 - dotted minim + _Play_TicksPerBar, _ '9 - semi-breve + _Play_TicksPerBar / 24, _ '10 - triplet semi-quaver + _Play_TicksPerBar / 12, _ '11 - triplet quaver + _Play_TicksPerBar / 6 _ '12 - triplet crotchet +} + +' Divider values that need to be sent to the audio chip registers to play the notes. +' Note that the lowest notes in octave 0 are unplayable because of 12-bit overflow and probably wrong notes will be +' played instead of them. +' TODO: replace them with maximum possible values or zeros? See how it's done in Sinclair Play. +' TODO: in Sinclair Play it is possible to play notes in higher octaves (using several sharps in a row). +' Need to add more values to the table. +dim _Play_NoteDividers(0 to _Play_NotesPerOctave * _Play_TotalOctaves - 1) as uinteger = { _ +_ ' C C# D D# E F F# G G# A A# B + 6211, 5863, 5534, 5223, 4930, 4653, 4392, 4145, 3913, 3693, 3486, 3290, _ 'octave 0 + 3106, 2931, 2767, 2611, 2465, 2327, 2196, 2073, 1956, 1847, 1743, 1645, _ 'octave 1 + 1553, 1466, 1383, 1306, 1232, 1163, 1098, 1036, 978, 923, 871, 823, _ 'octave 2 + 776, 733, 692, 653, 616, 582, 549, 518, 489, 462, 436, 411, _ 'octave 3 + 388, 366, 346, 326, 308, 291, 274, 259, 245, 231, 218, 206, _ 'octave 4 + 194, 183, 173, 163, 154, 145, 137, 130, 122, 115, 109, 103, _ 'octave 5 + 97, 92, 86, 82, 77, 73, 69, 65, 61, 58, 54, 51, _ 'octave 6 + 49, 46, 43, 41, 39, 36, 34, 32, 31, 29, 27, 26, _ 'octave 7 + 24, 23, 22, 20, 19, 18, 17, 16, 15, 14, 14, 13 _ 'octave 8 +} + +' Maps ascii code of a letter to the corresponding index of `_Play_NoteDividers` (octave 0). +dim _Play_NoteIndexes(code("A") to code("G")) as ubyte = { _ + /'A'/ 9, _ + /'B'/ 11, _ + /'C'/ 0, _ + /'D'/ 2, _ + /'E'/ 4, _ + /'F'/ 5, _ + /'G'/ 7 _ +} + +' Pointer to the current channel context. +' Made global for better performance, and also because it would be problematic to access it from nested subs if it were +' local (see 'Implementation note' on `Play`). +dim _Play_ContextPtr as uinteger + +' Switches to the first channel context. +#define _PLAY_CTX_FIRST_CHANNEL() let _Play_ContextPtr = ChannelContextBufferPtr + +' Swithes to the next channel context. +#define _PLAY_CTX_NEXT_CHANNEL() let _Play_ContextPtr = _Play_ContextPtr + ChannelContextSize + +' Gets the value of the given `type` at the given `offset` of the current channel context. +#define _PLAY_CTX_GET(type, offset) (peek(type, _Play_ContextPtr + (offset))) + +' Sets the given `value` of the given `type` to the given `offset` of the current channel context. +#define _PLAY_CTX_SET(type, offset, value) poke type _Play_ContextPtr + (offset), (value) + +' Arithmetically adds the given `value` to the value of the given `type` stored at the given `offset` +' of the current channel context. +#define _PLAY_CTX_ADD(type, offset, value) _PLAY_CTX_SET(type, offset, _PLAY_CTX_GET(type, offset) + (value)) + +' Write the value to the given register of the sound chip. +#define _PLAY_WRITE_TO_REGISTER(register, value) out $cf, (register) : out $0f, (value) + + +' Main sub. +' +' Implementation note: +' If you want to extend the inner subs or functions, or add new ones, +' please beware that the current compiler version (v1.19.0-beta7 at the time of writing) +' doesn't support accessing outer local vars from the inner sub/function, +' if the inner sub/function has its own vars or params. +' +' TODO: add sub variants that accept strings byref, and that accept an array. +' TODO: check valid ranges of command parameters, handle integer overflow/underflow +' +sub Play(channel0 as string, channel1 as string = "", channel2 as string = "") + + const CpuCyclesPerSecond as ulong = 3250000 + const CpuCyclesPerMicrotick as ubyte = 27 ' see the `Wait` sub + const BeatsPerBar as ubyte = 4 + const SecondsPerMinute as ubyte = 60 + const ChannelCount as ubyte = 3 + + const DefaultTempo as ubyte = 120 + const DefaultOctave as ubyte = 5 + const DefaultNoteLength as ubyte = 5 + const DefaultVolume as ubyte = 15 + const DefaultMixer as ubyte = %11111000 + + ' General processing overhead compensation. Applied to every `Wait` invocation. + ' Determined experimentally. + ' TODO: adjust if needed after everything is implemented. + const TickGeneralOverheadInMicroticks as uinteger = 90 + + ' Overhead compensation for commands processing of a single channel. + ' Applied only on those ticks when there are commands processed for the channel. + ' Determined experimentally. + ' TODO: adjust if needed after everything is implemented. + const TickChannelCommandsOverheadInMicroticks as uinteger = 140 + + ' Channel numbers are zero-based, because it's better in terms of performance (less arithmetics in runtime needed). + const MaxChannel as ubyte = ChannelCount - 1 + + ' Size of a single channel context in bytes. Don't forget to increase this if you add more context fields. + ' Note: this is used in macro `_PLAY_CTX_NEXT_CHANNEL`. + const ChannelContextSize as ubyte = 15 + + ' Channel context data is stored here. + dim ChannelContextBuffer(0 to ChannelContextSize * ChannelCount - 1) as ubyte + + ' Pointer to context data. + ' Note: this is used in macro `_PLAY_CTX_FIRST_CHANNEL`. + dim ChannelContextBufferPtr as uinteger + ChannelContextBufferPtr = @ChannelContextBuffer(0) + + ' Offsets of fields in a channel context. + ' If you add more fields, don't forget to increase `ChannelContextSize`. + const _CharPtr as ubyte = 0 ' (uinteger) Pointer to the current character in the channel string. + const _StringEndPtr as ubyte = 2 ' (uinteger) Pointer to the first byte after the last char of the channel + ' string. + const _TickBackCounter as ubyte = 4 ' (uinteger) How many ticks to wait before proceeding to the next command + ' in the channel string. Zero means we need to proceed now. + const _PrimaryNoteLengthInTicks as ubyte = 6 ' (uinteger) Current note length in ticks. + const _ActualNoteLengthInTicks as ubyte = 8 ' (uinteger) Actual note length in ticks. + ' Mostly the same as `_PrimaryNoteLengthInTicks`, + ' but may differ for triplets and ties. + const _ResetNoteLengthBackCount as ubyte = 10 ' (ubyte) How many notes left to play before actual note length must + ' be reset to primary note length. + const _BaseDividerIndex as ubyte = 11 ' (ubyte) Divider index (see `_Play_NoteDividers`) that corresponds to + ' note C of the current octave. + const _SemitoneAdjustment as ubyte = 12 ' (byte) How many semitones to add or subtract from the next note. + const _FinishedFlag as ubyte = 13 ' (ubyte) If nonzero, then the channel has finished playing. + const _Volume as ubyte = 14 ' (ubyte) Current volume. + + ' Current tempo as beats per minute. A 'beat' is a 1/4-length note. + dim Tempo as ubyte + + ' Current tempo as microticks per tick. + ' For 'microtick' definition, see the `CpuCyclesPerMicrotick` const. + ' For 'tick' definition, see the `_Play_TicksPerBar` const. + dim MicroticksPerTick as uinteger + + dim LastChar as ubyte ' Last char read by `ReadChar` sub. + dim LastNumber as uinteger ' Last number read by `ReadNumber` sub. + + ' Reads a char from the current channel string and puts it to `LastChar` variable. + ' Puts 0 if there's nothing left to read. + ' This is a sub, not a function, for performance reasons. + sub ReadChar + if _PLAY_CTX_GET(uinteger, _CharPtr) = _PLAY_CTX_GET(uinteger, _StringEndPtr) then + LastChar = 0 + return + end if + + LastChar = peek(_PLAY_CTX_GET(uinteger, _CharPtr)) + _PLAY_CTX_ADD(uinteger, _CharPtr, 1) + end sub + + ' Reads the number from the current channel string and puts it in `LastNumber` variable. + ' Puts 0 if the number is unreadable. + sub ReadNumber + LastNumber = 0 + + do + ReadChar + + if LastChar >= code("0") and LastChar <= code("9") then + LastNumber = LastNumber * 10 + LastChar - code("0") + else + ' The number has ended. + if LastChar <> 0 then + ' Step back if the string has not ended. + _PLAY_CTX_ADD(uinteger, _CharPtr, -1) + end if + exit do + end if + loop + end sub + + ' Sets octave for the current channel. + sub SetOctave(octave as ubyte) + _PLAY_CTX_SET(ubyte, _BaseDividerIndex, octave * _Play_NotesPerOctave) + end sub + + ' Sets `MicroticksPerTick` according to the current `Tempo` value. + sub UpdateMicroticksPerTick + MicroticksPerTick = _ + CpuCyclesPerSecond * SecondsPerMinute * BeatsPerBar _ + / CpuCyclesPerMicrotick / _Play_TicksPerBar / Tempo + end sub + + ' Waits for the given amount of microticks. + ' One microtick is 27 CPU cycles (if you change it, also change `CpuCyclesPerMicrotick` const). + ' TODO: is it possible to suppress the 'unused parameter' warning? + sub fastcall Wait(microticks as uinteger) + asm + proc + local loop + + ld bc, 1 ; bc = 1 + or a ; reset carry flag + loop: + sbc hl, bc ; microticks = microticks - bc ; 15 cycles + jr nz, loop ; if microticks <> 0 then goto loop ; 12 cycles + + endp + end asm + end sub + + ' Set pitch on the sound chip for a channel. + sub SetChipPitchDivider(channel as ubyte, divider as uinteger) + _PLAY_WRITE_TO_REGISTER(channel * 2, divider band $ff) + _PLAY_WRITE_TO_REGISTER(channel * 2 + 1, divider >> 8) + end sub + + ' Set volume on the sound chip for a channel. + sub SetChipVolume(channel as ubyte, volume as ubyte) + _PLAY_WRITE_TO_REGISTER(channel + 8, volume) + end sub + + ' Set mixer mode on the sound chip. + sub SetChipMixer(value as ubyte) + _PLAY_WRITE_TO_REGISTER(7, value) + end sub + + ' If macro `_PLAY_BENCHMARK_MODE` is defined, then interrupts are not disabled, and the system timer is used to + ' measure the duration of play. The duration in ticks is printed on the screen after playing. + ' Note that interrupts add overhead and inaccuracies to timings, so this mode is not intended for fine-tuning + ' timings. This should only be used for differential analysis of code optimization efficiency. + #ifndef _PLAY_BENCHMARK_MODE + asm + di + end asm + #endif + + _PLAY_CTX_FIRST_CHANNEL() + _PLAY_CTX_SET(uinteger, _CharPtr, @channel0) + + _PLAY_CTX_NEXT_CHANNEL() + _PLAY_CTX_SET(uinteger, _CharPtr, @channel1) + + _PLAY_CTX_NEXT_CHANNEL() + _PLAY_CTX_SET(uinteger, _CharPtr, @channel2) + + dim channel as ubyte + dim strLen as uinteger + + _PLAY_CTX_FIRST_CHANNEL() + + for channel = 0 to MaxChannel + ' We need low-level access to the strings to achieve good performance. + + ' dereference the pointer to the heap + _PLAY_CTX_SET(uinteger, _CharPtr, peek(uinteger, _PLAY_CTX_GET(uinteger, _CharPtr))) + + ' read the string length + strLen = peek(uinteger, _PLAY_CTX_GET(uinteger, _CharPtr)) + + ' adjust the pointer so it points to the first char + _PLAY_CTX_ADD(uinteger, _CharPtr, 2) + + ' calculate and store the pointer to the string end + _PLAY_CTX_SET(uinteger, _StringEndPtr, _PLAY_CTX_GET(uinteger, _CharPtr) + strLen) + + SetOctave DefaultOctave + _PLAY_CTX_SET(ubyte, _Volume, DefaultVolume) + _PLAY_CTX_SET(uinteger, _PrimaryNoteLengthInTicks, _Play_NoteLengthsInTicks(DefaultNoteLength)) + _PLAY_CTX_SET(uinteger, _ActualNoteLengthInTicks, _Play_NoteLengthsInTicks(DefaultNoteLength)) + _PLAY_CTX_SET(uinteger, _TickBackCounter, 0) + _PLAY_CTX_SET(byte, _SemitoneAdjustment, 0) + _PLAY_CTX_SET(ubyte, _ResetNoteLengthBackCount, 0) + _PLAY_CTX_SET(ubyte, _FinishedFlag, 0) + + _PLAY_CTX_NEXT_CHANNEL() + next channel + + Tempo = DefaultTempo + UpdateMicroticksPerTick + SetChipMixer DefaultMixer + + dim processedChannels as ubyte + dim finishedChannels as ubyte + dim lengthInTicks as uinteger + dim dividerIndex as ubyte + + #ifdef _PLAY_BENCHMARK_MODE + dim SysFrames as uinteger at $5c78 + dim startTime as uinteger = SysFrames + #endif + + do + finishedChannels = 0 + processedChannels = 0 + + _PLAY_CTX_FIRST_CHANNEL() + + for channel = 0 to MaxChannel + if _PLAY_CTX_GET(uinteger, _TickBackCounter) = 0 then + do + ReadChar + + if LastChar = 0 then + ' This channel has finished playing. + _PLAY_CTX_SET(ubyte, _FinishedFlag, 1) + ' While other channels are still playing, this one will do rests. + SetChipVolume channel, 0 + exit do + + else if LastChar = code("&") then + SetChipVolume channel, 0 + exit do + + else if (LastChar >= code("a") and LastChar <= code("g")) _ + or (LastChar >= code("A") and LastChar <= code("G")) then + + dividerIndex = _PLAY_CTX_GET(ubyte, _BaseDividerIndex) + + if LastChar >= code("a") then + ' if lowercase, then transpose down 1 octave and make uppercase + dividerIndex = dividerIndex - _Play_NotesPerOctave + LastChar = LastChar - 32 + end if + + dividerIndex = dividerIndex + _ + _Play_NoteIndexes(LastChar) + _PLAY_CTX_GET(byte, _SemitoneAdjustment) + + _PLAY_CTX_SET(byte, _SemitoneAdjustment, 0) + + SetChipPitchDivider channel, _Play_NoteDividers(dividerIndex) + SetChipVolume channel, _PLAY_CTX_GET(ubyte, _Volume) + + exit do + + else if LastChar = code("$") then + _PLAY_CTX_ADD(byte, _SemitoneAdjustment, -1) + + else if LastChar = code("#") then + _PLAY_CTX_ADD(byte, _SemitoneAdjustment, 1) + + else if LastChar >= code("0") and LastChar <= code("9") + _PLAY_CTX_ADD(uinteger, _CharPtr, -1) ' step back + ReadNumber + lengthInTicks = _Play_NoteLengthsInTicks(LastNumber) + + _PLAY_CTX_SET(uinteger, _ActualNoteLengthInTicks, lengthInTicks) + + if LastNumber >= 10 then + ' triplet mode (temporary length) + _PLAY_CTX_SET(ubyte, _ResetNoteLengthBackCount, 3) + else + _PLAY_CTX_SET(uinteger, _PrimaryNoteLengthInTicks, lengthInTicks) + end if + + else if LastChar = code("_") then + ' TODO: ties with triplets work differently in Sinclair ROM. + ReadNumber + lengthInTicks = _Play_NoteLengthsInTicks(LastNumber) + + _PLAY_CTX_SET(ubyte, _ResetNoteLengthBackCount, 1) + _PLAY_CTX_ADD(uinteger, _ActualNoteLengthInTicks, lengthInTicks) + _PLAY_CTX_SET(uinteger, _PrimaryNoteLengthInTicks, lengthInTicks) + + else if LastChar = code("O") then + ReadNumber + SetOctave LastNumber + + else if LastChar = code("V") then + ReadNumber + _PLAY_CTX_SET(ubyte, _Volume, LastNumber) + + else if LastChar = code("T") then + ReadNumber + + if channel = 0 then + Tempo = LastNumber + UpdateMicroticksPerTick + end if + + ' TODO: process other commands here + end if + loop + + if _PLAY_CTX_GET(ubyte, _ResetNoteLengthBackCount) > 0 then + _PLAY_CTX_ADD(ubyte, _ResetNoteLengthBackCount, -1) + else + _PLAY_CTX_SET(uinteger, _ActualNoteLengthInTicks, _PLAY_CTX_GET(uinteger, _PrimaryNoteLengthInTicks)) + end if + + _PLAY_CTX_SET(uinteger, _TickBackCounter, _PLAY_CTX_GET(uinteger, _ActualNoteLengthInTicks)) + + processedChannels = processedChannels + 1 + end if + + _PLAY_CTX_ADD(uinteger, _TickBackCounter, -1) + + finishedChannels = finishedChannels + _PLAY_CTX_GET(ubyte, _FinishedFlag) + + _PLAY_CTX_NEXT_CHANNEL() + next channel + + Wait MicroticksPerTick _ + - TickGeneralOverheadInMicroticks _ + - TickChannelCommandsOverheadInMicroticks * processedChannels + loop until finishedChannels = ChannelCount + + #ifdef _PLAY_BENCHMARK_MODE + print "Play duration: "; SysFrames - startTime; " ticks." + #endif + + ' (zx81sd) Sin EI final: el runtime funciona con las interrupciones + ' deshabilitadas y rehabilitarlas podria disparar una INT sin handler. +end sub + +#pragma pop(explicit) +#pragma pop(strict) From bc6f618d3fa32c24ae69ca73ae235335b5728640 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Thu, 2 Jul 2026 21:16:26 +0200 Subject: [PATCH 06/27] zx81sd: libreria de comandos del MCU del SD81 Booster (mcu.bas, joy.bas) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mcu.bas — primitivas del protocolo del MCU (puertos $A7 datos / $AF reloj) con las transferencias en ensamblador (McuSend/McuRecv/ McuSendBlock/McuRecvBlock: camino critico de LOAD/SAVE/F_READ/F_WRITE) y wrappers para todos los comandos del manual: - Sistema: NOP, VERSION, GETBYTE, SETBYTE - Ficheros: PWD, CD, DEL, MKDIR, RMDIR, MOVE, COPY, LOAD, SAVE, TYPE, DIR, FREE (texto y binario), OPENDIR/GETROWLEN/GETROW - Handles (32 bits): F_OPEN (ASCII), F_OPEN_ZX81, F_SEEK, F_READ, F_WRITE, F_CLOSE — verificado contra el firmware (COMMANDS.cpp): el MCU asigna el handle y lo devuelve - Hardware: MC45, 128C/64C, FULLPAGING/HALFPAGING, RAM48, y el mapeador de memoria Map/MapGet (puerto $E7, valido en ambos modos de paginacion con una sola escritura) - Voz: SAY, BINARY_SAY - AY del MCU (chip 2): AY_SET_REG/AY_GET_REG/AY_PLAY (con conversion de octavas mayuscula<->video inverso como el interprete del MCU) - VGM y PEG completos; RTC y BAT - Extensiones no-MCU del BASIC del SD81: HexPoke (*HEX), StrInv/StrBold (*INV/*BOLD); *LDIR/*LDDR cubiertos por MemMove de la stdlib estandar Conversion automatica ASCII<->codigos ZX81 en los comandos de texto. joy.bas (comando 21) reescrito como capa fina sobre mcu.bas. Verificado en EightyOne(SD81): version, get/setbyte, pwd, save+load con verificacion byte a byte, del, free, rtc, bat, dir, AY2 por registros, AyPlay, joystick, mapeador y HexPoke/MemMove/StrInv. Co-Authored-By: Claude Fable 5 --- src/lib/arch/zx81sd/stdlib/joy.bas | 77 +++ src/lib/arch/zx81sd/stdlib/mcu.bas | 897 +++++++++++++++++++++++++++++ 2 files changed, 974 insertions(+) create mode 100644 src/lib/arch/zx81sd/stdlib/joy.bas create mode 100644 src/lib/arch/zx81sd/stdlib/mcu.bas diff --git a/src/lib/arch/zx81sd/stdlib/joy.bas b/src/lib/arch/zx81sd/stdlib/joy.bas new file mode 100644 index 000000000..2e48f8425 --- /dev/null +++ b/src/lib/arch/zx81sd/stdlib/joy.bas @@ -0,0 +1,77 @@ +' ---------------------------------------------------------------------- +' joy.bas (zx81sd) — Configuracion del joystick programable del SD81 +' +' El SD81 Booster mapea el joystick fisico a pulsaciones de teclado. +' El mapeo se configura con el comando 21 ($15) del MCU, que recibe 5 +' codigos de tecla ZX81: arriba, abajo, izquierda, derecha y fuego. +' +' Uso: +' #include +' dim st as ubyte +' st = Joy("QAOPM") ' arriba=Q abajo=A izq=O der=P fuego=M +' st = Joy("7657 ") ' cursores de Sinclair (fuego=espacio) +' +' La cadena debe tener exactamente 5 caracteres, en el orden +' ARRIBA, ABAJO, IZQUIERDA, DERECHA, FUEGO. Caracteres validos: +' letras (A-Z, indistinto mayus/minus), digitos (0-9) y espacio. +' +' Devuelve el byte de estado del MCU: 0 = OK, 14 = parametro invalido. +' Si la cadena es invalida (longitud o caracteres) devuelve 14 sin +' llegar a llamar al MCU. +' ---------------------------------------------------------------------- + +#pragma once + +#include + +#pragma push(explicit) +#pragma explicit = true + +const _JOY_CMD as ubyte = $15 ' comando 21 del MCU: configurar joystick + +' Convierte un caracter ASCII al codigo de tecla ZX81 que espera el MCU +' (0 = espacio, 28-37 = digitos, 38-63 = letras). +' Devuelve 255 si el caracter no corresponde a ninguna tecla valida. +function _JoyKeyCode(c as ubyte) as ubyte + if c = code(" ") then + return 0 + end if + if c >= code("0") and c <= code("9") then + return 28 + (c - code("0")) + end if + if c >= code("a") and c <= code("z") then + c = c - 32 + end if + if c >= code("A") and c <= code("Z") then + return 38 + (c - code("A")) + end if + return 255 +end function + +' Configura el mapeo del joystick programable. +' keys: 5 caracteres en orden ARRIBA, ABAJO, IZQUIERDA, DERECHA, FUEGO. +' Devuelve el estado del MCU (0 = OK). +function Joy(keys as string) as ubyte + dim i as ubyte + dim kc as ubyte + dim zxcodes as string + + if len(keys) <> 5 then + return 14 ' mismo codigo que usa el MCU + end if + + ' valida y convierte ANTES de tocar el MCU, para no dejar el + ' protocolo a medias si hay un caracter invalido + zxcodes = "" + for i = 0 to 4 + kc = _JoyKeyCode(code(keys(i))) + if kc = 255 then + return 14 + end if + zxcodes = zxcodes + chr(kc) + next i + + return McuCmdStr(_JOY_CMD, zxcodes) +end function + +#pragma pop(explicit) diff --git a/src/lib/arch/zx81sd/stdlib/mcu.bas b/src/lib/arch/zx81sd/stdlib/mcu.bas new file mode 100644 index 000000000..2c5d41408 --- /dev/null +++ b/src/lib/arch/zx81sd/stdlib/mcu.bas @@ -0,0 +1,897 @@ +' ---------------------------------------------------------------------- +' mcu.bas (zx81sd) — Comunicacion con el MCU del SD81 Booster +' +' Primitivas del protocolo + wrappers de los comandos del MCU. +' Los comandos con nombre propio (joy.bas, etc.) se apoyan en esta +' libreria. +' +' Protocolo (puertos $A7 datos / $AF reloj): +' El bit 7 del puerto $AF es el reloj de sincronizacion: el MCU lo +' invierte cuando ha procesado cada lectura/escritura del puerto de +' datos $A7. Tras cada operacion en $A7 hay que esperar ese cambio +' antes de la siguiente. +' +' ATENCION: escribir CUALQUIER valor en $AF resetea el MCU (peligroso +' si esta escribiendo en la SD). Esta libreria solo LEE $AF. +' +' Las transferencias de datos estan implementadas en ensamblador (son +' el camino critico de LOAD/SAVE/F_READ/F_WRITE); el BASIC solo prepara +' parametros y convierte codificaciones. +' +' Codificacion: los comandos de fichero/texto viajan en codigos de +' caracter ZX81 (el MCU los convierte internamente). Esta libreria +' convierte automaticamente desde ASCII. Excepciones ya en crudo: +' F_OPEN (ASCII), BINARY_SAY (alofonos) y JOY (codigos de tecla). +' +' Convencion de resultados: las funciones devuelven el byte de estado +' del MCU (0 = exito; ver tabla de errores del manual). Las que +' devuelven datos (McuLoad, McuPwd, ...) dejan el estado en la variable +' global McuStatus. +' ---------------------------------------------------------------------- + +#pragma once + +#pragma push(explicit) +#pragma explicit = true + +' Estado devuelto por el ultimo comando "con datos" (McuLoad, McuPwd...) +dim McuStatus as ubyte + +' Resultado de McuFree() en KB +dim McuFreeTotalKb as ulong +dim McuFreeFreeKb as ulong + +' ====================================================================== +' PRIMITIVAS DEL PROTOCOLO (ASM) +' ====================================================================== + +' Envia un byte al MCU y espera a que lo procese. +sub fastcall McuSend(value as ubyte) + asm + PROC + LOCAL WAIT + ; A = value (convencion fastcall para ubyte) + push bc + ld b, a ; salva el dato + in a, ($AF) + ld c, a ; C = estado previo del reloj (bit 7) + ld a, b + out ($A7), a ; envia + WAIT: + in a, ($AF) + xor c + jp p, WAIT ; espera a que el bit 7 cambie + pop bc + ENDP + end asm +end sub + +' Lee un byte del MCU (respuesta o status) y espera el toggle posterior. +function fastcall McuRecv() as ubyte + asm + PROC + LOCAL WAIT + push bc + in a, ($AF) + ld c, a ; reloj previo + in a, ($A7) ; lee el dato (provoca el toggle del MCU) + ld b, a + WAIT: + in a, ($AF) + xor c + jp p, WAIT + ld a, b ; devuelve el dato en A (fastcall ubyte) + pop bc + ENDP + end asm +end function + +' Envia un bloque de memoria al MCU (un byte por transaccion de reloj). +' Bucle integramente en ASM: camino critico de SAVE / F_WRITE. +sub fastcall McuSendBlock(addr as uinteger, size as uinteger) + asm + PROC + LOCAL LOOP, WAIT, DONE + ; HL = addr (fastcall); pila: [ret][size] + pop de ; DE = direccion de retorno + pop bc ; BC = size + push de ; restaura el retorno + + ld a, b + or c + jr z, DONE + LOOP: + in a, ($AF) + ld e, a ; E = reloj previo + ld a, (hl) + out ($A7), a + WAIT: + in a, ($AF) + xor e + jp p, WAIT + inc hl + dec bc + ld a, b + or c + jr nz, LOOP + DONE: + ENDP + end asm +end sub + +' Recibe un bloque del MCU en memoria (un byte por transaccion de reloj). +' Bucle integramente en ASM: camino critico de LOAD / F_READ. +sub fastcall McuRecvBlock(addr as uinteger, size as uinteger) + asm + PROC + LOCAL LOOP, WAIT, DONE + ; HL = addr (fastcall); pila: [ret][size] + pop de + pop bc + push de + + ld a, b + or c + jr z, DONE + LOOP: + in a, ($AF) + ld e, a ; reloj previo + in a, ($A7) ; lee dato + ld (hl), a + WAIT: + in a, ($AF) + xor e + jp p, WAIT + inc hl + dec bc + ld a, b + or c + jr nz, LOOP + DONE: + ENDP + end asm +end sub + +' ====================================================================== +' CONVERSION ASCII <-> CODIGOS DE CARACTER ZX81 +' ====================================================================== + +' ASCII -> codigo ZX81. Caracteres sin equivalente -> '?' ($0F). +function _McuToZx(c as ubyte) as ubyte + if c >= 97 and c <= 122 then + c = c - 32 ' minusculas a mayusculas + end if + if c >= 65 and c <= 90 then + return 38 + (c - 65) ' A-Z + end if + if c >= 48 and c <= 57 then + return 28 + (c - 48) ' 0-9 + end if + if c = 32 then return 0 ' espacio + if c = 34 then return 11 ' " + if c = 36 then return 13 ' $ + if c = 58 then return 14 ' : + if c = 63 then return 15 ' ? + if c = 40 then return 16 ' ( + if c = 41 then return 17 ' ) + if c = 62 then return 18 ' > + if c = 60 then return 19 ' < + if c = 61 then return 20 ' = + if c = 43 then return 21 ' + + if c = 45 then return 22 ' - + if c = 42 then return 23 ' * + if c = 47 then return 24 ' / + if c = 59 then return 25 ' ; + if c = 44 then return 26 ' , + if c = 46 then return 27 ' . + return 15 ' resto: '?' +end function + +' Codigo ZX81 -> ASCII (ignora el bit 7 de video inverso). +function _McuFromZx(z as ubyte) as ubyte + z = z band 127 + if z >= 38 and z <= 63 then + return 65 + (z - 38) ' A-Z + end if + if z >= 28 and z <= 37 then + return 48 + (z - 28) ' 0-9 + end if + if z = 0 then return 32 ' espacio + if z = 11 then return 34 ' " + if z = 13 then return 36 ' $ + if z = 14 then return 58 ' : + if z = 15 then return 63 ' ? + if z = 16 then return 40 ' ( + if z = 17 then return 41 ' ) + if z = 18 then return 62 ' > + if z = 19 then return 60 ' < + if z = 20 then return 61 ' = + if z = 21 then return 43 ' + + if z = 22 then return 45 ' - + if z = 23 then return 42 ' * + if z = 24 then return 47 ' / + if z = 25 then return 59 ' ; + if z = 26 then return 44 ' , + if z = 27 then return 46 ' . + return 63 ' resto: '?' +end function + +' Convierte una cadena ASCII completa a codigos ZX81. +function McuZxStr(s as string) as string + dim i as uinteger + dim r as string + r = "" + for i = 0 to len(s) - 1 + r = r + chr(_McuToZx(code(s(i)))) + next i + return r +end function + +' ====================================================================== +' PATRONES GENERICOS +' ====================================================================== + +' Envia una cadena Pascal (longitud + bytes crudos, max 255). +sub _McuSendPascal(dat as string) + dim addr as uinteger + dim l as uinteger + addr = peek(uinteger, @dat) + l = peek(uinteger, addr) + if l > 255 then l = 255 + McuSend(cast(ubyte, l)) + if l > 0 then + McuSendBlock(addr + 2, l) + end if +end sub + +' Comando sin parametros ni respuesta. +sub McuCmd(cmd as ubyte) + McuSend(cmd) +end sub + +' Comando + cadena en crudo + status. +function McuCmdStr(cmd as ubyte, dat as string) as ubyte + McuSend(cmd) + _McuSendPascal(dat) + return McuRecv() +end function + +' Comando + cadena (convertida a ZX81) + status. Para comandos de +' fichero/texto (CD, DEL, LOAD, SAY...). +function McuCmdStrZx(cmd as ubyte, dat as string) as ubyte + return McuCmdStr(cmd, McuZxStr(dat)) +end function + +' Recibe un stream de caracteres (protocolo NEXTCH $0D hasta EOT $6F) +' y lo devuelve como string ASCII. $76 (newline ZX81) -> CHR(13). +' Deja el status en McuStatus. Para respuestas cortas (PWD, RTC...). +function _McuRecvStream() as string + dim c as ubyte + dim r as string + r = "" + do + McuSend($0D) ' CMD_NEXTCH: pide un caracter + c = McuRecv() + if c = $6F then ' EOT + exit do + end if + if c = $76 then + r = r + chr(13) ' nueva linea ZX81 + else + r = r + chr(_McuFromZx(c)) + end if + loop + McuStatus = McuRecv() ' byte de estado final + return r +end function + +' Recibe un stream y lo imprime linea a linea (para listados largos: +' DIR, TYPE, FREE_TXT). Devuelve el status. +function _McuStreamPrint() as ubyte + dim c as ubyte + dim line as string + line = "" + do + McuSend($0D) + c = McuRecv() + if c = $6F then + exit do + end if + if c = $76 then + print line + line = "" + else + line = line + chr(_McuFromZx(c)) + end if + loop + if len(line) > 0 then + print line + end if + return McuRecv() +end function + +' ====================================================================== +' COMANDOS DE SISTEMA +' ====================================================================== + +' Cmd 0: sin operacion (sincroniza el reloj). +sub McuNop() + McuCmd(0) +end sub + +' Cmd 1: version del firmware del MCU. +function McuVersion() as ubyte + McuSend(1) + return McuRecv() +end function + +' Cmd 32: lee un byte de la memoria interna del MCU. +' Indices 0-127: variables volatiles. 128-255: EEPROM (persistente). +function McuGetByte(index as ubyte) as ubyte + McuSend(32) + McuSend(index) + return McuRecv() +end function + +' Cmd 33: escribe un byte en la memoria interna del MCU. +sub McuSetByte(index as ubyte, value as ubyte) + McuSend(33) + McuSend(index) + McuSend(value) +end sub + +' ====================================================================== +' COMANDOS DE SISTEMA DE ARCHIVOS +' ====================================================================== + +' Cmd 2: directorio actual. Status en McuStatus. +function McuPwd() as string + McuSend(2) + return _McuRecvStream() +end function + +' Cmd 3: cambia de directorio (rutas absolutas con / o relativas). +function McuCd(path as string) as ubyte + return McuCmdStrZx(3, path) +end function + +' Cmd 4: borra un archivo del directorio actual. +function McuDel(fname as string) as ubyte + return McuCmdStrZx(4, fname) +end function + +' Cmd 5: crea un subdirectorio. +function McuMkdir(dname as string) as ubyte + return McuCmdStrZx(5, dname) +end function + +' Cmd 6: elimina un directorio vacio. +function McuRmdir(dname as string) as ubyte + return McuCmdStrZx(6, dname) +end function + +' Cmd 7/8: renombra-mueve / copia un archivo (dos cadenas seguidas). +function _McuTwoStr(cmd as ubyte, src as string, dst as string) as ubyte + McuSend(cmd) + _McuSendPascal(McuZxStr(src)) + _McuSendPascal(McuZxStr(dst)) + return McuRecv() +end function + +function McuMove(src as string, dst as string) as ubyte + return _McuTwoStr(7, src, dst) +end function + +function McuCopy(src as string, dst as string) as ubyte + return _McuTwoStr(8, src, dst) +end function + +' Cmd 9: carga un archivo de la SD en memoria a partir de addr. +' Devuelve el numero de bytes cargados (0 si error); status en McuStatus. +' Ojo: extensiones especiales — .ROM resetea la CPU (no retorna), +' .WAV se reproduce (devuelve 0 bytes). +function McuLoad(fname as string, addr as uinteger) as uinteger + dim lo as ubyte + dim hi as ubyte + dim size as uinteger + + McuSend(9) + _McuSendPascal(McuZxStr(fname)) + lo = McuRecv() + hi = McuRecv() + size = cast(uinteger, hi) * 256 + lo + if size > 0 then + McuRecvBlock(addr, size) + end if + McuStatus = McuRecv() + return size +end function + +' Cmd 10: guarda un bloque de memoria como archivo en la SD. +function McuSave(fname as string, addr as uinteger, size as uinteger) as ubyte + McuSend(10) + _McuSendPascal(McuZxStr(fname)) + McuSend(cast(ubyte, size band 255)) + McuSend(cast(ubyte, size >> 8)) + if size > 0 then + McuSendBlock(addr, size) + end if + return McuRecv() +end function + +' Cmd 11: imprime el contenido de un archivo de texto. +' Si el nombre empieza por '*' busca en /MAN/ con extension .TXT. +function McuTypePrint(fname as string) as ubyte + McuSend(11) + _McuSendPascal(McuZxStr(fname)) + return _McuStreamPrint() +end function + +' Cmd 12: imprime el listado de un directorio (admite comodines). +function McuDirPrint(mask as string) as ubyte + McuSend(12) + _McuSendPascal(McuZxStr(mask)) + return _McuStreamPrint() +end function + +' Cmd 14: imprime el espacio total/libre de la SD como texto. +function McuFreeTxtPrint() as ubyte + McuSend(14) + return _McuStreamPrint() +end function + +' Cmd 15: espacio total y libre en KB -> McuFreeTotalKb / McuFreeFreeKb. +' Devuelve el status. +function McuFree() as ubyte + dim i as ubyte + dim v as ulong + + McuSend(15) + v = 0 + for i = 0 to 3 + v = v bor (cast(ulong, McuRecv()) << (i * 8)) + next i + McuFreeTotalKb = v + v = 0 + for i = 0 to 3 + v = v bor (cast(ulong, McuRecv()) << (i * 8)) + next i + McuFreeFreeKb = v + return McuRecv() +end function + +' Cmds 16/17/18: OPENDIR / GETROWLEN / GETROW — navegador de directorio. +' ATENCION: no emulados por EightyOne a fecha de hoy (solo HW real); +' en el emulador se quedarian esperando la respuesta. +function McuOpenDir(mask as string) as ubyte + return McuCmdStrZx(16, mask) +end function + +' Devuelve la longitud del nombre de la entrada index; status en McuStatus. +function McuGetRowLen(index as uinteger) as ubyte + dim l as ubyte + McuSend(17) + McuSend(cast(ubyte, index band 255)) + McuSend(cast(ubyte, index >> 8)) + l = McuRecv() + McuStatus = McuRecv() + return l +end function + +' Devuelve el nombre de la entrada index (0 = directorio actual); +' los directorios van entre < y >. Status en McuStatus. +function McuGetRow(index as uinteger) as string + dim l as ubyte + dim i as ubyte + dim r as string + McuSend(18) + McuSend(cast(ubyte, index band 255)) + McuSend(cast(ubyte, index >> 8)) + l = McuRecv() + r = "" + if l > 0 then + for i = 1 to l + r = r + chr(_McuFromZx(McuRecv())) + next i + end if + McuStatus = McuRecv() + return r +end function + +' ====================================================================== +' ACCESO A FICHEROS GRANDES (handles 0-3, tamano 32 bits) +' ====================================================================== + +' Cmd 53: abre un fichero EXISTENTE para acceso aleatorio. El MCU asigna +' el primer handle libre y lo devuelve (0-3, o $FF si error/no existe). +' El nombre viaja en ASCII crudo (sin conversion; es el fopen de CP/M). +function McuFOpen(fname as string) as ubyte + McuSend(53) + _McuSendPascal(fname) ' ASCII directo, sin conversion + return McuRecv() +end function + +' Cmd 58: igual que McuFOpen pero el nombre viaja en codigos ZX81 +' (la conversion desde ASCII la hace esta funcion). +function McuFOpenZx(fname as string) as ubyte + McuSend(58) + _McuSendPascal(McuZxStr(fname)) + return McuRecv() +end function + +' Cmd 54: situa el puntero de lectura/escritura (offset 32 bits LE). +function McuFSeek(handle as ubyte, offset as ulong) as ubyte + dim i as ubyte + McuSend(54) + McuSend(handle) + for i = 0 to 3 + McuSend(cast(ubyte, (offset >> (i * 8)) band 255)) + next i + return McuRecv() +end function + +' Cmd 55: lee count bytes al buffer addr. Si el fichero se acaba, +' rellena con ceros (status 1 = lectura corta/EOF). +function McuFRead(handle as ubyte, addr as uinteger, count as uinteger) as ubyte + McuSend(55) + McuSend(handle) + McuSend(cast(ubyte, count band 255)) + McuSend(cast(ubyte, count >> 8)) + if count > 0 then + McuRecvBlock(addr, count) + end if + return McuRecv() +end function + +' Cmd 56: escribe count bytes desde addr. +function McuFWrite(handle as ubyte, addr as uinteger, count as uinteger) as ubyte + McuSend(56) + McuSend(handle) + McuSend(cast(ubyte, count band 255)) + McuSend(cast(ubyte, count >> 8)) + if count > 0 then + McuSendBlock(addr, count) + end if + return McuRecv() +end function + +' Cmd 57: cierra el fichero. +function McuFClose(handle as ubyte) as ubyte + McuSend(57) + McuSend(handle) + return McuRecv() +end function + +' ====================================================================== +' CONTROL DEL HARDWARE +' ====================================================================== +' ATENCION: los cambios de paginacion/RAM pueden dejar sin sentido el +' mapa de memoria del programa en ejecucion. Usar solo sabiendo lo que +' se hace. + +' Mapeador de memoria del SD81 (puerto $E7, directo a la FPGA — no pasa +' por el MCU). Equivale al LOAD *MAP bloque,pagina del BASIC. +' +' El espacio de 64K se divide en 8 bloques de 8K (bloque 0 = $0000-$1FFF +' ... bloque 7 = $E000-$FFFF); cada bloque puede apuntar a cualquier +' pagina fisica de 8K de la RAM de 512K (0-31 en paginacion simple, +' 0-63 en paginacion completa — ver McuFullPaging/McuHalfPaging). +' +' La escritura vale para AMBOS modos: pone la pagina en B (A8-A13, que +' es de donde la lee el modo completo) Y en los bits 7-3 del dato (de +' donde la lee el modo simple). +' +' CUIDADO: remapear un bloque donde vive el propio programa, las +' sysvars ($8000, bloque 4) o la pantalla ($C000, bloque 6) cuelga o +' corrompe el sistema. +sub fastcall Map(block as ubyte, page as ubyte) + asm + ; A = block (fastcall); pila: [ret][page en byte alto] + pop hl ; direccion de retorno + pop de ; D = page + push hl + and 7 + ld l, a ; L = bloque (0-7) + ld b, d ; B = pagina completa (aparece en A8-A15) + ld c, $E7 + ld a, d + and 31 + rlca + rlca + rlca ; (pagina AND 31) << 3 + or l ; dato = pagina<<3 | bloque (modo simple) + out (c), a ; una sola escritura vale para ambos modos + end asm +end sub + +' Lee la pagina actualmente asignada a un bloque (lectura de $E7 con +' el bloque en A10-A8). +function fastcall MapGet(block as ubyte) as ubyte + asm + ; A = block (fastcall) + and 7 + ld b, a ; B aparece en A8-A15 durante IN A,(C) + ld c, $E7 + in a, (c) ; A = pagina asignada (resultado fastcall) + end asm +end function + +sub McuEnableMc45() + McuCmd(19) +end sub + +sub McuDisableMc45() + McuCmd(20) +end sub + +sub McuSel128Chars() + McuCmd(27) +end sub + +sub McuSel64Chars() + McuCmd(28) +end sub + +sub McuFullPaging() + McuCmd(29) +end sub + +sub McuHalfPaging() + McuCmd(30) +end sub + +sub McuRam48On() + McuCmd(48) +end sub + +sub McuRam48Off() + McuCmd(49) +end sub + +' ====================================================================== +' SINTESIS DE VOZ +' ====================================================================== + +' Cmd 23: convierte texto a fonemas y lo reproduce. +' Si el primer caracter es '*', reproduce en background. +function McuSay(text as string) as ubyte + return McuCmdStrZx(23, text) +end function + +' Cmd 22: reproduce alofonos SP0256-AL2 en crudo (codigos $00-$3F). +' Sincrono: bloquea hasta terminar. +function McuBinarySay(allophones as string) as ubyte + return McuCmdStr(22, allophones) +end function + +' ====================================================================== +' AY POR MCU (chip 2: el AY "B" del SD81) Y PLAY EN BACKGROUND +' ====================================================================== + +' Cmd 24: escribe un registro del AY del MCU (chip 2). Sin status. +sub McuAySetReg(reg as ubyte, value as ubyte) + McuSend(24) + McuSend(reg) + McuSend(value) +end sub + +' Cmd 25: lee un registro del AY del MCU (chip 2). +function McuAyGetReg(reg as ubyte) as ubyte + McuSend(25) + McuSend(reg) + return McuRecv() +end function + +' Convierte una cadena MML de PLAY conservando la semantica de octavas +' del interprete del MCU: minuscula ASCII -> letra ZX81 normal +' (octava base - 1), MAYUSCULA ASCII -> letra ZX81 en video inverso +' (octava base). Igual que el PLAY local de play.bas. +function _McuPlayStr(s as string) as string + dim i as uinteger + dim c as ubyte + dim r as string + r = "" + for i = 0 to len(s) - 1 + c = code(s(i)) + if c >= 65 and c <= 90 then + r = r + chr(128 + 38 + (c - 65)) ' mayuscula: inverso + else + r = r + chr(_McuToZx(c)) + end if + next i + return r +end function + +' Cmd 26: PLAY de hasta 3 canales en el AY del MCU (chip 2). +' Si el canal A empieza por '*', reproduce en background. +function McuAyPlay(chanA as string, chanB as string, chanC as string) as ubyte + McuSend(26) + _McuSendPascal(_McuPlayStr(chanA)) + _McuSendPascal(_McuPlayStr(chanB)) + _McuSendPascal(_McuPlayStr(chanC)) + return McuRecv() +end function + +' ====================================================================== +' REPRODUCTOR VGM +' ====================================================================== + +' Cmd 34: abre un archivo VGM (anade .vgm si no tiene extension) y lo +' prepara; iniciar con McuContVgm(). +function McuPlayVgm(fname as string) as ubyte + return McuCmdStrZx(34, fname) +end function + +sub McuStopVgm() + McuCmd(35) +end sub + +sub McuPauseVgm() + McuCmd(36) +end sub + +sub McuContVgm() + McuCmd(37) +end sub + +' Cmd 38: modo de bucle (0 = no, 1 = si). Sin status. +sub McuLoopVgm(mode as ubyte) + McuSend(38) + McuSend(mode) +end sub + +' ====================================================================== +' PEG (Programmable Effects Generator) +' ====================================================================== + +' Cmd 40: carga instrucciones en la memoria PEG a partir de address. +' dat contiene los bytes crudos (2 bytes little-endian por instruccion). +' Sin status. +sub McuLoadPeg(address as ubyte, dat as string) + McuSend(40) + McuSend(address) + _McuSendPascal(dat) +end sub + +' Cmd 41: arranca un hilo PEG (0-2) en la direccion dada. Sin status. +sub McuPlayPeg(thread as ubyte, address as ubyte) + McuSend(41) + McuSend(thread) + McuSend(address) +end sub + +sub McuStopPeg(thread as ubyte) + McuSend(42) + McuSend(thread) +end sub + +sub McuPausePeg(thread as ubyte) + McuSend(43) + McuSend(thread) +end sub + +sub McuContPeg(thread as ubyte) + McuSend(44) + McuSend(thread) +end sub + +' Cmd 45: carga un archivo .PEB de la SD en la memoria PEG (max 512B). +function McuSdLoadPeg(fname as string, address as ubyte) as ubyte + McuSend(45) + _McuSendPascal(McuZxStr(fname)) + McuSend(address) + return McuRecv() +end function + +' ====================================================================== +' RTC Y BATERIA +' ====================================================================== + +' Cmd 50 (lectura): devuelve "AAAA-MM-DD HH:MM:SS.CC" (22 caracteres). +' Status en McuStatus. +function McuRtc() as string + dim i as ubyte + dim r as string + McuSend(50) + McuSend(0) ' cadena vacia = lectura + r = "" + for i = 1 to 22 + r = r + chr(_McuFromZx(McuRecv())) + next i + McuStatus = McuRecv() + return r +end function + +' Cmd 50 (escritura): ajusta el reloj. Formatos: "AAAA-MM-DD HH:MM:SS", +' "AAAA-MM-DD", "HH:MM:SS", "HH:MM", etc. +function McuRtcSet(datetime as string) as ubyte + return McuCmdStrZx(50, datetime) +end function + +' Cmd 52: nivel de bateria del RTC como texto "V.mmm" (5 caracteres). +' Status en McuStatus. +function McuBat() as string + dim i as ubyte + dim r as string + McuSend(52) + r = "" + for i = 1 to 5 + r = r + chr(_McuFromZx(McuRecv())) + next i + McuStatus = McuRecv() + return r +end function + +' ====================================================================== +' UTILIDADES (equivalentes de las extensiones BASIC del SD81) +' ====================================================================== +' Para *LDIR/*LDDR usar MemMove de (stdlib estandar), que +' ademas elige el sentido de copia correcto cuando hay solapamiento. +' Para *OUT/*IN y los PEEK/POKE de 16 bits, zxbasic ya tiene OUT, IN(), +' peek(uinteger, ...) y poke uinteger nativos. + +' Valor de un digito hexadecimal ASCII, o 255 si no lo es. +function _HexDigit(c as ubyte) as ubyte + if c >= 48 and c <= 57 then + return c - 48 ' 0-9 + end if + if c >= 65 and c <= 70 then + return c - 55 ' A-F + end if + if c >= 97 and c <= 102 then + return c - 87 ' a-f + end if + return 255 +end function + +' Equivalente de LOAD *HEX: vuelca una cadena hexadecimal en memoria +' ("0A014020" -> bytes $0A,$01,$40,$20 a partir de addr). +' Devuelve el numero de bytes escritos (se detiene en el primer +' caracter no hexadecimal o si la cadena tiene longitud impar). +function HexPoke(addr as uinteger, hx as string) as uinteger + dim i as uinteger + dim n as uinteger + dim hi as ubyte + dim lo as ubyte + + n = 0 + i = 0 + do while i + 2 <= len(hx) + hi = _HexDigit(code(hx(i))) + lo = _HexDigit(code(hx(i + 1))) + if hi = 255 or lo = 255 then + exit do + end if + poke addr + n, (hi << 4) bor lo + n = n + 1 + i = i + 2 + loop + return n +end function + +' Equivalente de LOAD *INV: invierte el bit 7 de todos los caracteres. +' Nota: en zxbasic el video inverso al imprimir se hace con PRINT +' INVERSE; esto sirve para preparar buffers en codificacion ZX81 +' (p.ej. cadenas para McuAyPlay o para escribir en pantalla nativa). +function StrInv(s as string) as string + dim i as uinteger + dim r as string + r = "" + for i = 0 to len(s) - 1 + r = r + chr(code(s(i)) bxor 128) + next i + return r +end function + +' Equivalente de LOAD *BOLD: fuerza a 1 el bit 7 de todos los caracteres. +function StrBold(s as string) as string + dim i as uinteger + dim r as string + r = "" + for i = 0 to len(s) - 1 + r = r + chr(code(s(i)) bor 128) + next i + return r +end function + +#pragma pop(explicit) From 75c8f7f7c3f9a118b0ac1b898ad7b60f01ad50b7 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Thu, 2 Jul 2026 23:38:36 +0200 Subject: [PATCH 07/27] zx81sd: statements LOAD/SAVE/VERIFY ... CODE contra la SD del SD81 Sustituyen a los load.asm/save.asm de zx48k (rutinas de cinta de la ROM Spectrum, inutilizables sin ROM): ahora LOAD/SAVE/VERIFY ... CODE operan sobre la tarjeta SD mediante los comandos 9/10 del MCU. - io/sd81_mcu.asm: primitivas del protocolo del MCU para el runtime (send/recv con espera de reloj, bloques, RECV_VERIFY que compara sin escribir, RECV_SINK para descartar sobrante manteniendo el protocolo sincronizado, conversion ASCII->ZX81 de nombres). - load.asm: LOAD carga el fichero completo (longitud 0) o hasta N bytes (descarta el exceso; fichero mas corto = error). VERIFY compara sin escribir. Errores blandos como en cinta: ERR_NR = 26 (Tape loading) o 14 (nombre invalido) y retorna. - save.asm: SAVE guarda el bloque; longitud 0 = InvalidArg. Interfaz del compilador identica a zx48k (incluida la liberacion del string del nombre con MEM_FREE). Verificado en EightyOne(SD81): ciclo SAVE+LOAD con verificacion byte a byte, VERIFY correcto y con byte corrupto (ERR 26), y carga de fichero inexistente (ERR 26). Co-Authored-By: Claude Fable 5 --- src/lib/arch/zx81sd/runtime/io/sd81_mcu.asm | 274 ++++++++++++++++++++ src/lib/arch/zx81sd/runtime/load.asm | 160 ++++++++++++ src/lib/arch/zx81sd/runtime/save.asm | 100 +++++++ 3 files changed, 534 insertions(+) create mode 100644 src/lib/arch/zx81sd/runtime/io/sd81_mcu.asm create mode 100644 src/lib/arch/zx81sd/runtime/load.asm create mode 100644 src/lib/arch/zx81sd/runtime/save.asm diff --git a/src/lib/arch/zx81sd/runtime/io/sd81_mcu.asm b/src/lib/arch/zx81sd/runtime/io/sd81_mcu.asm new file mode 100644 index 000000000..1e473e009 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/io/sd81_mcu.asm @@ -0,0 +1,274 @@ +; sd81_mcu.asm (zx81sd) — Primitivas del protocolo del MCU del SD81 Booster +; para uso desde el RUNTIME en ensamblador (load.asm / save.asm). +; +; (La stdlib mcu.bas lleva sus propias copias de estas rutinas dentro de +; SUBs fastcall; aqui viven las de los statements LOAD/SAVE del compilador.) +; +; Protocolo: puerto $A7 = datos, puerto $AF = reloj (bit 7). El MCU +; invierte el bit de reloj cuando procesa cada lectura/escritura de $A7; +; tras cada operacion hay que esperar ese cambio. NUNCA escribir en $AF +; (provoca un reset del MCU). + + push namespace core + +SD81_MCU_DATA EQU $A7 +SD81_MCU_CLOCK EQU $AF + +; --------------------------------------------------------------------------- +; SD81_MCU_SEND — Envia el byte A al MCU y espera a que lo procese. +; Preserva BC, DE, HL. Modifica AF. +; --------------------------------------------------------------------------- +SD81_MCU_SEND: + PROC + LOCAL WAIT + push bc + ld b, a ; salva el dato + in a, (SD81_MCU_CLOCK) + ld c, a ; C = reloj previo + ld a, b + out (SD81_MCU_DATA), a +WAIT: + in a, (SD81_MCU_CLOCK) + xor c + jp p, WAIT + pop bc + ret + ENDP + +; --------------------------------------------------------------------------- +; SD81_MCU_RECV — Lee un byte del MCU en A y espera el toggle posterior. +; Preserva BC, DE, HL. Modifica AF. +; --------------------------------------------------------------------------- +SD81_MCU_RECV: + PROC + LOCAL WAIT + push bc + in a, (SD81_MCU_CLOCK) + ld c, a ; reloj previo + in a, (SD81_MCU_DATA) ; lee el dato (dispara el toggle del MCU) + ld b, a +WAIT: + in a, (SD81_MCU_CLOCK) + xor c + jp p, WAIT + ld a, b + pop bc + ret + ENDP + +; --------------------------------------------------------------------------- +; SD81_MCU_SEND_BLOCK — Envia BC bytes desde (HL) al MCU. +; Modifica AF, BC, E, HL. Devuelve HL apuntando tras el bloque. +; --------------------------------------------------------------------------- +SD81_MCU_SEND_BLOCK: + PROC + LOCAL LOOP, WAIT + ld a, b + or c + ret z +LOOP: + in a, (SD81_MCU_CLOCK) + ld e, a ; reloj previo + ld a, (hl) + out (SD81_MCU_DATA), a +WAIT: + in a, (SD81_MCU_CLOCK) + xor e + jp p, WAIT + inc hl + dec bc + ld a, b + or c + jr nz, LOOP + ret + ENDP + +; --------------------------------------------------------------------------- +; SD81_MCU_RECV_BLOCK — Recibe BC bytes del MCU en (HL). +; Modifica AF, BC, E, HL. +; --------------------------------------------------------------------------- +SD81_MCU_RECV_BLOCK: + PROC + LOCAL LOOP, WAIT + ld a, b + or c + ret z +LOOP: + in a, (SD81_MCU_CLOCK) + ld e, a + in a, (SD81_MCU_DATA) + ld (hl), a +WAIT: + in a, (SD81_MCU_CLOCK) + xor e + jp p, WAIT + inc hl + dec bc + ld a, b + or c + jr nz, LOOP + ret + ENDP + +; --------------------------------------------------------------------------- +; SD81_MCU_RECV_VERIFY — Recibe BC bytes y los compara con (HL). +; Devuelve A = 0 si todo coincide, A = 1 si hubo diferencias. +; (Consume siempre los BC bytes para no desincronizar el protocolo.) +; Modifica AF, BC, DE, HL. +; --------------------------------------------------------------------------- +SD81_MCU_RECV_VERIFY: + PROC + LOCAL LOOP, WAIT, SAME + ld d, 0 ; flag de diferencias + ld a, b + or c + jr z, SAME +LOOP: + in a, (SD81_MCU_CLOCK) + ld e, a + in a, (SD81_MCU_DATA) + cp (hl) + jr z, WAIT + ld d, 1 ; difiere +WAIT: + in a, (SD81_MCU_CLOCK) + xor e + jp p, WAIT + inc hl + dec bc + ld a, b + or c + jr nz, LOOP +SAME: + ld a, d + ret + ENDP + +; --------------------------------------------------------------------------- +; SD81_MCU_RECV_SINK — Recibe y descarta BC bytes (mantiene el protocolo +; sincronizado cuando el fichero es mayor que el buffer del llamador). +; Modifica AF, BC, E. +; --------------------------------------------------------------------------- +SD81_MCU_RECV_SINK: + PROC + LOCAL LOOP, WAIT + ld a, b + or c + ret z +LOOP: + in a, (SD81_MCU_CLOCK) + ld e, a + in a, (SD81_MCU_DATA) +WAIT: + in a, (SD81_MCU_CLOCK) + xor e + jp p, WAIT + dec bc + ld a, b + or c + jr nz, LOOP + ret + ENDP + +; --------------------------------------------------------------------------- +; SD81_ASC2ZX — Convierte el caracter ASCII en A a codigo de caracter +; ZX81 (los nombres de fichero de los comandos del MCU viajan en ZX81). +; Sin equivalente -> '?' ($0F). Preserva BC, DE, HL. +; --------------------------------------------------------------------------- +SD81_ASC2ZX: + PROC + LOCAL NOT_LOWER, NOT_UPPER, NOT_DIGIT, TABLE, TLOOP, TDONE + cp 'a' + jr c, NOT_LOWER + cp 'z' + 1 + jr nc, NOT_LOWER + sub 32 ; a mayuscula +NOT_LOWER: + cp 'A' + jr c, NOT_UPPER + cp 'Z' + 1 + jr nc, NOT_UPPER + sub 'A' - 38 ; letras: codigos 38-63 + ret +NOT_UPPER: + cp '0' + jr c, NOT_DIGIT + cp '9' + 1 + jr nc, NOT_DIGIT + sub '0' - 28 ; digitos: codigos 28-37 + ret +NOT_DIGIT: + push hl + push bc + ld hl, TABLE + ld b, (TDONE - TABLE) / 2 +TLOOP: + cp (hl) + inc hl + jr z, TDONE ; encontrado: (HL) = codigo ZX81 + inc hl + djnz TLOOP + ld a, $0F ; sin equivalente: '?' + pop bc + pop hl + ret +TDONE: + ld a, (hl) + pop bc + pop hl + ret + +TABLE: ; pares ASCII, codigo ZX81 + defb ' ', $00 + defb '.', $1B + defb '/', $18 + defb '-', $16 + defb '*', $17 + defb '<', $13 + defb '>', $12 + defb '(', $10 + defb ')', $11 + defb '$', $0D + defb ':', $0E + defb ',', $1A + defb ';', $19 + defb '+', $15 + defb '=', $14 + defb '"', $0B + ENDP + +; --------------------------------------------------------------------------- +; SD81_MCU_SEND_NAME — Envia un string de zxbasic (prefijo de longitud de +; 16 bits + datos) como cadena Pascal para el MCU: byte de longitud +; (recortada a 255) + caracteres convertidos a codigos ZX81. +; Entrada: HL = puntero al string (al prefijo de longitud). HL != 0. +; Modifica AF, BC, HL. +; --------------------------------------------------------------------------- +SD81_MCU_SEND_NAME: + PROC + LOCAL LOOP, LENOK + ld c, (hl) + inc hl + ld b, (hl) + inc hl ; BC = longitud, HL -> caracteres + ld a, b + or a + jr z, LENOK + ld c, 255 ; recorta a 255 (limite del protocolo) +LENOK: + ld a, c + call SD81_MCU_SEND ; longitud + ld a, c + or a + ret z + ld b, c +LOOP: + ld a, (hl) + call SD81_ASC2ZX + call SD81_MCU_SEND + inc hl + djnz LOOP + ret + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/load.asm b/src/lib/arch/zx81sd/runtime/load.asm new file mode 100644 index 000000000..21f858551 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/load.asm @@ -0,0 +1,160 @@ +; load.asm (zx81sd) — LOAD/VERIFY ... CODE desde la tarjeta SD +; +; Sustituye a zx48k/runtime/load.asm (que usa LD-BYTES de cinta, puerto +; $FE y cabeceras de cassette del Spectrum). Aqui el statement LOAD de +; zxbasic carga el fichero desde la SD del SD81 Booster con el comando 9 +; del MCU (ver io/sd81_mcu.asm para el protocolo). +; +; Interfaz del compilador (identica a zx48k): +; pila: A = 1 LOAD / 0 VERIFY; BC = longitud; DE = direccion destino; +; HL = string de zxbasic con el nombre (hay que liberarlo). +; +; Semantica: +; - BC = 0: carga el fichero completo en DE. +; - BC > 0: carga como maximo BC bytes en DE; si el fichero es mayor +; se descarta el resto (el protocolo obliga a consumirlo); si es +; menor se marca error de carga. +; - VERIFY: compara en vez de escribir; diferencia = error de carga. +; - Error (fichero no existe, etc.): ERR_NR = ERROR_TapeLoadingErr y +; se retorna (mismo comportamiento "blando" que la version de cinta). +; - Ojo con extensiones especiales del MCU: un .ROM resetea la CPU +; (no retorna) y un .WAV solo se reproduce (carga 0 bytes). + +#include once +#include once +#include once + + push namespace core + +LOAD_CODE: + PROC + LOCAL LC_FLAG, LC_REQ, LC_DEST, LC_SIZE, LC_N + LOCAL LC_HAVE_SIZE, LC_USE_REQ, LC_DO_RECV, LC_DRAIN + LOCAL LC_ERROR, LC_END, LC_ERR_NOFREE + LOCAL LC_VERIFY + + pop hl ; direccion de retorno + pop af ; A = 1 LOAD / 0 VERIFY + pop bc ; longitud solicitada + pop de ; direccion destino + ex (sp), hl ; CALLEE: HL = string con el nombre + + ld (LC_FLAG), a + ld (LC_REQ), bc + ld (LC_DEST), de + + ld a, h + or l + jp z, LC_ERR_NOFREE ; string nulo + + push hl ; salva el puntero para MEM_FREE + + ; longitud del nombre 0 -> error (tras liberar) + ld e, (hl) + inc hl + ld d, (hl) + dec hl + ld a, d + or e + jr nz, LC_HAVE_SIZE + + pop hl + call MEM_FREE + jp LC_ERR_NOFREE + +LC_HAVE_SIZE: + ; comando 9 (LOAD) + nombre + ld a, 9 + call SD81_MCU_SEND + call SD81_MCU_SEND_NAME + pop hl + call MEM_FREE ; el nombre ya viajo: libera el string + + ; tamano real del fichero (little-endian) + call SD81_MCU_RECV + ld l, a + call SD81_MCU_RECV + ld h, a + ld (LC_SIZE), hl + + ; n = (LC_REQ = 0) ? size : min(LC_REQ, size) + ld bc, (LC_REQ) + ld a, b + or c + jr z, LC_DO_RECV ; BC=0: n = size (ya en HL) + or a + sbc hl, bc ; size - req + jr nc, LC_USE_REQ ; size >= req: n = req + ld hl, (LC_SIZE) ; size < req: n = size (y sera error corto) + jr LC_DO_RECV +LC_USE_REQ: + ld hl, (LC_REQ) + +LC_DO_RECV: + ld (LC_N), hl + ld b, h + ld c, l ; BC = n + ld hl, (LC_DEST) + ld a, (LC_FLAG) + or a + jr z, LC_VERIFY + + call SD81_MCU_RECV_BLOCK + xor a ; sin diferencias + jr LC_DRAIN + +LC_VERIFY: + call SD81_MCU_RECV_VERIFY ; A = 1 si hubo diferencias + +LC_DRAIN: + push af ; salva el resultado de la verificacion + ; descarta lo que sobre: size - n + ld hl, (LC_SIZE) + ld bc, (LC_N) + or a + sbc hl, bc + ld b, h + ld c, l + call SD81_MCU_RECV_SINK + + call SD81_MCU_RECV ; byte de estado del MCU + ld e, a + pop af + or e ; error si status != 0 o verificacion fallida + jp nz, LC_ERROR + + ; carga corta: se pidieron LC_REQ (>0) bytes y el fichero era menor + ld bc, (LC_REQ) + ld a, b + or c + jr z, LC_END + ld hl, (LC_N) + or a + sbc hl, bc + jr z, LC_END ; n = req: completo + +LC_ERROR: + ld a, ERROR_TapeLoadingErr + ld (ERR_NR), a +LC_END: + ret + +LC_ERR_NOFREE: + ld a, ERROR_InvalidFileName + ld (ERR_NR), a + ret + +LC_FLAG: + defb 0 +LC_REQ: + defw 0 +LC_DEST: + defw 0 +LC_SIZE: + defw 0 +LC_N: + defw 0 + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/save.asm b/src/lib/arch/zx81sd/runtime/save.asm new file mode 100644 index 000000000..0b9457960 --- /dev/null +++ b/src/lib/arch/zx81sd/runtime/save.asm @@ -0,0 +1,100 @@ +; save.asm (zx81sd) — SAVE ... CODE a la tarjeta SD +; +; Sustituye a zx48k/runtime/save.asm (que usa SA-BYTES de cinta de la +; ROM Spectrum). El statement SAVE de zxbasic guarda el bloque en la SD +; del SD81 Booster con el comando 10 del MCU (ver io/sd81_mcu.asm). +; +; Interfaz del compilador (identica a zx48k): +; pila: BC = longitud; DE = direccion origen; +; HL = string de zxbasic con el nombre (hay que liberarlo). +; +; Errores ("blandos", como la version de cinta): nombre nulo/vacio -> +; ERROR_InvalidFileName; longitud 0 -> ERROR_InvalidArg; fallo del MCU +; (SD llena, etc.) -> ERROR_TapeLoadingErr. En todos se retorna con +; ERR_NR puesto. + +#include once +#include once +#include once + + push namespace core + +SAVE_CODE: + PROC + LOCAL SC_LEN, SC_SRC + LOCAL SC_HAVE_NAME, SC_ERR_NAME_NOFREE, SC_DO_SAVE + + pop hl ; direccion de retorno + pop bc ; longitud + pop de ; direccion origen + ex (sp), hl ; CALLEE: HL = string con el nombre + + ld (SC_LEN), bc + ld (SC_SRC), de + + ld a, h + or l + jp z, SC_ERR_NAME_NOFREE ; string nulo + + ; longitud del bloque 0 -> error (liberando el string) + ld a, b + or c + jr nz, SC_HAVE_NAME + call MEM_FREE + ld a, ERROR_InvalidArg + ld (ERR_NR), a + ret + +SC_HAVE_NAME: + ; nombre vacio -> error (tras liberar) + ld e, (hl) + inc hl + ld d, (hl) + dec hl + ld a, d + or e + jr nz, SC_DO_SAVE + + call MEM_FREE + jp SC_ERR_NAME_NOFREE + +SC_DO_SAVE: + ; comando 10 (SAVE) + nombre + longitud + datos + status + push hl + ld a, 10 + call SD81_MCU_SEND + pop hl + push hl + call SD81_MCU_SEND_NAME + pop hl + call MEM_FREE + + ld bc, (SC_LEN) + ld a, c + call SD81_MCU_SEND ; longitud, byte bajo + ld a, b + call SD81_MCU_SEND ; longitud, byte alto + + ld hl, (SC_SRC) + call SD81_MCU_SEND_BLOCK + + call SD81_MCU_RECV ; byte de estado + or a + ret z + ld a, ERROR_TapeLoadingErr + ld (ERR_NR), a + ret + +SC_ERR_NAME_NOFREE: + ld a, ERROR_InvalidFileName + ld (ERR_NR), a + ret + +SC_LEN: + defw 0 +SC_SRC: + defw 0 + + ENDP + + pop namespace From 9903c8662bc336a9b1498e89b81c9e9f605cf3b4 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Fri, 3 Jul 2026 13:14:39 +0200 Subject: [PATCH 08/27] zx81sd: area de UDGs dedicada (los POKE USR CHR$ corrompian el runtime) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La sysvar UDG apuntaba a __ZX81SD_CHARSET+896 asumiendo una fuente completa de 256 caracteres, pero specfont.bin solo cubre CHR$(32)- CHR$(127) (768 bytes): el puntero caia 128 bytes mas alla del final de la fuente, sobre el codigo del runtime que tocara despues en el enlazado. Cualquier programa que definiera UDGs (POKE USR CHR$(144+n)) machacaba ese codigo — p.ej. comecoquitos.bas corrompia CALC_TEST_ROOM de fp_calc y colgaba el sistema al primer uso del calculador. Fix: charset.asm reserva un area de 21 UDGs (CHR$(144)-CHR$(164), 168 bytes) tras la fuente, y SD81_INIT_SYSVARS apunta UDG a ella y la inicializa con copias de las letras A-U, igual que la ROM del Spectrum. Co-Authored-By: Claude Fable 5 --- src/lib/arch/zx81sd/runtime/bootstrap.asm | 17 +++++++++++++---- src/lib/arch/zx81sd/runtime/charset.asm | 9 +++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/lib/arch/zx81sd/runtime/bootstrap.asm b/src/lib/arch/zx81sd/runtime/bootstrap.asm index e6b6b3cd7..a9ddd84f9 100644 --- a/src/lib/arch/zx81sd/runtime/bootstrap.asm +++ b/src/lib/arch/zx81sd/runtime/bootstrap.asm @@ -33,12 +33,21 @@ SD81_INIT_SYSVARS: ld hl, __ZX81SD_CHARSET - 256 ld (CHARS), hl - ; UDG: primer carácter definible por el usuario (CHR$(144) en Spectrum) - ; = font base + (144-32)*8 = font + 896. Con la convención CHARS-256: - ; UDG = CHARS + 144*8 = __ZX81SD_CHARSET - 256 + 1152 = __ZX81SD_CHARSET + 896 - ld hl, __ZX81SD_CHARSET + 896 + ; UDG: área dedicada de 21 caracteres definibles (CHR$(144)-CHR$(164), + ; como en el Spectrum), reservada en charset.asm DESPUÉS de la fuente. + ; (La fuente sólo cubre CHR$(32)-CHR$(127): el antiguo "font+896" + ; apuntaba 128 bytes más allá de su final, sobre código del runtime, + ; y los POKE USR CHR$ de un programa lo corrompían.) + ld hl, __ZX81SD_UDG_AREA ld (UDG), hl + ; Inicializa los UDGs con copias de las letras A-U (como la ROM del + ; Spectrum): glifo de 'A' = font + (65-32)*8 = font + 264. + ld hl, __ZX81SD_CHARSET + 264 + ld de, __ZX81SD_UDG_AREA + ld bc, 21 * 8 + ldir + ; Cursor al inicio de pantalla (columna=SCR_COLS, fila=SCR_ROWS) ld a, SCR_ROWS ld h, a diff --git a/src/lib/arch/zx81sd/runtime/charset.asm b/src/lib/arch/zx81sd/runtime/charset.asm index 20a0cad25..9cc665859 100644 --- a/src/lib/arch/zx81sd/runtime/charset.asm +++ b/src/lib/arch/zx81sd/runtime/charset.asm @@ -8,4 +8,13 @@ __ZX81SD_CHARSET: INCBIN "specfont.bin" +; Area de UDGs (CHR$(144) a CHR$(164), 21 caracteres como en el Spectrum). +; La fuente solo cubre CHR$(32)-CHR$(127) (768 bytes): sin este bloque, +; apuntar UDG a "font+896" caia 128 bytes MAS ALLA del final de la fuente, +; sobre el codigo que tocara despues en el enlazado (los POKE USR CHR$ +; de un programa corrompian el runtime). SD81_INIT_SYSVARS la inicializa +; con copias de las letras A-U, igual que la ROM del Spectrum. +__ZX81SD_UDG_AREA: + defs 21 * 8 + pop namespace From 9a24059e77bc10de2e81efaba74ab7d890f612f7 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Fri, 3 Jul 2026 13:45:44 +0200 Subject: [PATCH 09/27] zx81sd: INKEY$ en minusculas (semantica Spectrum) y fix de PO_GR_1 - keyscan.asm: las letras se decodifican ahora en minuscula, igual que el INKEY$ del Spectrum en modo L (el por defecto). Los programas de la epoca comparan INKEY$ con "o","p","q","a","y","n"... y con mayusculas no respondian (p.ej. examples/comecoquitos.bas). - po_gr_1.asm: reescrito con el algoritmo literal de PO-GR-2 ($0B3E, ROM Spectrum). La version anterior tenia un OR aplicado al registro equivocado (machacaba los bits de cuadrante y dejaba vacia la mitad inferior de CHR$(143), entre otros) y el mapeo izquierda/derecha de los cuadrantes invertido respecto a la ROM (bit0 = superior DERECHO). Verificado con comecoquitos.bas completo (jugable, identico al .tap de Spectrum) y un test de los 16 graficos CHR$(128)-CHR$(143). Co-Authored-By: Claude Fable 5 --- .../zx81sd/runtime/io/keyboard/keyscan.asm | 354 +++++++++--------- src/lib/arch/zx81sd/runtime/po_gr_1.asm | 99 ++--- 2 files changed, 213 insertions(+), 240 deletions(-) diff --git a/src/lib/arch/zx81sd/runtime/io/keyboard/keyscan.asm b/src/lib/arch/zx81sd/runtime/io/keyboard/keyscan.asm index b51b36803..2c5809320 100644 --- a/src/lib/arch/zx81sd/runtime/io/keyboard/keyscan.asm +++ b/src/lib/arch/zx81sd/runtime/io/keyboard/keyscan.asm @@ -1,175 +1,179 @@ -; --------------------------------------------------------------------------- -; keyscan.asm — Escaneo directo de la matriz de teclado del ZX81 -; -; No hay ROM mapeada en tiempo de ejecucion (el bloque 0 lo ocupa por -; completo nuestro binario compilado), asi que no se puede llamar a la -; rutina KEYBOARD/DECODE de la ROM original. El hardware del teclado, en -; cambio, es el mismo ZX81 de siempre (el SD81 Booster no lo toca), asi -; que se reimplementa aqui el escaneo fisico (puerto $FEFE, 8 filas -; seleccionadas rotando el registro alto del puerto) y una decodificacion -; propia a ASCII. -; -; IMPORTANTE: la ROM del ZX81 decodifica las teclas a SU PROPIO charset -; (con codigos de token para palabras clave de BASIC), no a ASCII. El -; runtime de zx81sd usa el charset Spectrum/ASCII para imprimir (ver -; charset.asm, specfont.bin), asi que la tabla de decodificacion de abajo -; traduce directamente cada tecla a su codigo ASCII, ignorando las teclas -; que en el ZX81 producen tokens de BASIC sin equivalente ASCII simple -; (STOP, AND, OR, THEN, TO, STEP, <=, >=, <>, **, EDIT, GRAPHICS, -; FUNCTION, cursores, LPRINT, LLIST, SLOW, FAST). La tecla RUBOUT -; (SHIFT+0) se traduce a ASCII 12, y ENTER (NEWLINE) a ASCII 13, para -; mantener compatibilidad con la convencion ya usada por stdlib/input.bas. -; --------------------------------------------------------------------------- - - push namespace core - -; --------------------------------------------------------------------------- -; __ZX81SD_KEYSCAN -; -; Escanea las 8 filas de la matriz y decodifica la primera tecla -; encontrada (si hay varias pulsadas a la vez, solo se detecta una). -; -; Devuelve: -; A = codigo ASCII de la tecla pulsada, o 0 si no hay ninguna pulsada, -; o si la tecla no tiene equivalente ASCII (ver nota arriba), o si -; solo esta pulsada la tecla SHIFT sola. -; Flag Z activo si A = 0 -; -; Registros modificados: AF, BC, DE, HL -; --------------------------------------------------------------------------- -__ZX81SD_KEYSCAN: - PROC - LOCAL ROW_LOOP - LOCAL NEXT_ROW - LOCAL FIND_COL - LOCAL GOT_COL0 - LOCAL GOT_COL1 - LOCAL GOT_COL2 - LOCAL GOT_COL3 - LOCAL GOT_COL4 - LOCAL GOT_KEY - LOCAL LOOKUP - LOCAL NO_KEY - LOCAL UNSHIFT_TABLE - LOCAL SHIFT_TABLE - - ; D = indice de fila actual (0-7), E = flag SHIFT pulsado (0/1) - ld d, 0 - ld e, 0 - ld b, $FE ; B = mitad alta del puerto ($FEFE, $FDFE, ... $7FFE) - -ROW_LOOP: - ld c, $FE - in a, (c) ; lee la fila; bits 0-4 = columnas (0 = pulsada) - and $1F - cp $1F - jr z, NEXT_ROW ; ninguna tecla pulsada en esta fila - - ld l, a ; L = mapa de bits de columnas pulsadas - - ld a, d - or a - jr nz, FIND_COL - - ; Fila 0: la columna 0 es la tecla SHIFT (no genera caracter propio) - bit 0, l - jr nz, FIND_COL ; SHIFT no pulsado, comprobar columnas normalmente - ld e, 1 ; SHIFT pulsado - set 0, l ; descartar ese bit para no confundirlo con datos - ld a, l - cp $1F - jr z, NEXT_ROW ; en esta fila solo estaba pulsado SHIFT - -FIND_COL: - ; Bucle desenrollado a proposito: usar CP para comparar el contador - ; de columna sobreescribia A (el mapa de bits que se iba rotando) - ; antes de que la siguiente vuelta pudiera comprobarlo, con lo que - ; solo la columna 0 se detectaba bien. Sin bucle no hay ese riesgo. - ld a, l - rrca - jr nc, GOT_COL0 - rrca - jr nc, GOT_COL1 - rrca - jr nc, GOT_COL2 - rrca - jr nc, GOT_COL3 - rrca - jr nc, GOT_COL4 - jr NEXT_ROW ; no deberia ocurrir (ya se comprobo cp $1F antes) - -GOT_COL0: - ld c, 0 - jr GOT_KEY -GOT_COL1: - ld c, 1 - jr GOT_KEY -GOT_COL2: - ld c, 2 - jr GOT_KEY -GOT_COL3: - ld c, 3 - jr GOT_KEY -GOT_COL4: - ld c, 4 - -GOT_KEY: - ; indice de tabla = fila*5 + columna - 1 - ; (la fila 0 solo aporta 4 teclas: columnas 1-4, de ahi el -1) - ld a, d - add a, a - add a, a - add a, d ; A = fila*5 - add a, c ; A = fila*5 + columna - dec a ; A = indice (0-38) - - ld hl, UNSHIFT_TABLE - bit 0, e - jr z, LOOKUP - ld hl, SHIFT_TABLE - -LOOKUP: - ld c, a - ld b, 0 - add hl, bc - ld a, (hl) - or a ; fija flag Z segun corresponda - ret - -NEXT_ROW: - rlc b - inc d - ld a, d - cp 8 - jr nz, ROW_LOOP - -NO_KEY: - xor a - ret - - ; Orden de filas/columnas identico al de las tablas K-UNSHIFT/K-SHIFT - ; de la ROM original del ZX81 (fila 0: SHIFT,Z,X,C,V ... fila 7: - ; ENTER,L,K,J,H / SPACE,.,M,N,B), verificado contra el disassembly. -UNSHIFT_TABLE: - DEFB 'Z', 'X', 'C', 'V' - DEFB 'A', 'S', 'D', 'F', 'G' - DEFB 'Q', 'W', 'E', 'R', 'T' - DEFB '1', '2', '3', '4', '5' - DEFB '0', '9', '8', '7', '6' - DEFB 'P', 'O', 'I', 'U', 'Y' - DEFB 13, 'L', 'K', 'J', 'H' ; NEWLINE -> ENTER (ASCII 13) - DEFB ' ', '.', 'M', 'N', 'B' - -SHIFT_TABLE: - DEFB ':', ';', '?', '/' - DEFB 0, 0, 0, 0, 0 ; STOP, LPRINT, SLOW, FAST, LLIST - DEFB '"', 0, 0, 0, 0 ; "" (par de comillas), OR, STEP, <=, <> - DEFB 0, 0, 0, 0, 0 ; EDIT, AND, THEN, TO, cursor-izq - DEFB 12, 0, 0, 0, 0 ; RUBOUT (DEL=12), GRAPHICS, cursor der/arr/abj - DEFB '"', ')', '(', '$', 0 ; ", ), (, $, >= - DEFB 0, '=', '+', '-', 0 ; FUNCTION, =, +, -, ** - DEFB 0, ',', '>', '<', '*' ; £, ',', >, <, * - - ENDP - - pop namespace +; --------------------------------------------------------------------------- +; keyscan.asm — Escaneo directo de la matriz de teclado del ZX81 +; +; No hay ROM mapeada en tiempo de ejecucion (el bloque 0 lo ocupa por +; completo nuestro binario compilado), asi que no se puede llamar a la +; rutina KEYBOARD/DECODE de la ROM original. El hardware del teclado, en +; cambio, es el mismo ZX81 de siempre (el SD81 Booster no lo toca), asi +; que se reimplementa aqui el escaneo fisico (puerto $FEFE, 8 filas +; seleccionadas rotando el registro alto del puerto) y una decodificacion +; propia a ASCII. +; +; IMPORTANTE: la ROM del ZX81 decodifica las teclas a SU PROPIO charset +; (con codigos de token para palabras clave de BASIC), no a ASCII. El +; runtime de zx81sd usa el charset Spectrum/ASCII para imprimir (ver +; charset.asm, specfont.bin), asi que la tabla de decodificacion de abajo +; traduce directamente cada tecla a su codigo ASCII, ignorando las teclas +; que en el ZX81 producen tokens de BASIC sin equivalente ASCII simple +; (STOP, AND, OR, THEN, TO, STEP, <=, >=, <>, **, EDIT, GRAPHICS, +; FUNCTION, cursores, LPRINT, LLIST, SLOW, FAST). La tecla RUBOUT +; (SHIFT+0) se traduce a ASCII 12, y ENTER (NEWLINE) a ASCII 13, para +; mantener compatibilidad con la convencion ya usada por stdlib/input.bas. +; --------------------------------------------------------------------------- + + push namespace core + +; --------------------------------------------------------------------------- +; __ZX81SD_KEYSCAN +; +; Escanea las 8 filas de la matriz y decodifica la primera tecla +; encontrada (si hay varias pulsadas a la vez, solo se detecta una). +; +; Devuelve: +; A = codigo ASCII de la tecla pulsada, o 0 si no hay ninguna pulsada, +; o si la tecla no tiene equivalente ASCII (ver nota arriba), o si +; solo esta pulsada la tecla SHIFT sola. +; Flag Z activo si A = 0 +; +; Registros modificados: AF, BC, DE, HL +; --------------------------------------------------------------------------- +__ZX81SD_KEYSCAN: + PROC + LOCAL ROW_LOOP + LOCAL NEXT_ROW + LOCAL FIND_COL + LOCAL GOT_COL0 + LOCAL GOT_COL1 + LOCAL GOT_COL2 + LOCAL GOT_COL3 + LOCAL GOT_COL4 + LOCAL GOT_KEY + LOCAL LOOKUP + LOCAL NO_KEY + LOCAL UNSHIFT_TABLE + LOCAL SHIFT_TABLE + + ; D = indice de fila actual (0-7), E = flag SHIFT pulsado (0/1) + ld d, 0 + ld e, 0 + ld b, $FE ; B = mitad alta del puerto ($FEFE, $FDFE, ... $7FFE) + +ROW_LOOP: + ld c, $FE + in a, (c) ; lee la fila; bits 0-4 = columnas (0 = pulsada) + and $1F + cp $1F + jr z, NEXT_ROW ; ninguna tecla pulsada en esta fila + + ld l, a ; L = mapa de bits de columnas pulsadas + + ld a, d + or a + jr nz, FIND_COL + + ; Fila 0: la columna 0 es la tecla SHIFT (no genera caracter propio) + bit 0, l + jr nz, FIND_COL ; SHIFT no pulsado, comprobar columnas normalmente + ld e, 1 ; SHIFT pulsado + set 0, l ; descartar ese bit para no confundirlo con datos + ld a, l + cp $1F + jr z, NEXT_ROW ; en esta fila solo estaba pulsado SHIFT + +FIND_COL: + ; Bucle desenrollado a proposito: usar CP para comparar el contador + ; de columna sobreescribia A (el mapa de bits que se iba rotando) + ; antes de que la siguiente vuelta pudiera comprobarlo, con lo que + ; solo la columna 0 se detectaba bien. Sin bucle no hay ese riesgo. + ld a, l + rrca + jr nc, GOT_COL0 + rrca + jr nc, GOT_COL1 + rrca + jr nc, GOT_COL2 + rrca + jr nc, GOT_COL3 + rrca + jr nc, GOT_COL4 + jr NEXT_ROW ; no deberia ocurrir (ya se comprobo cp $1F antes) + +GOT_COL0: + ld c, 0 + jr GOT_KEY +GOT_COL1: + ld c, 1 + jr GOT_KEY +GOT_COL2: + ld c, 2 + jr GOT_KEY +GOT_COL3: + ld c, 3 + jr GOT_KEY +GOT_COL4: + ld c, 4 + +GOT_KEY: + ; indice de tabla = fila*5 + columna - 1 + ; (la fila 0 solo aporta 4 teclas: columnas 1-4, de ahi el -1) + ld a, d + add a, a + add a, a + add a, d ; A = fila*5 + add a, c ; A = fila*5 + columna + dec a ; A = indice (0-38) + + ld hl, UNSHIFT_TABLE + bit 0, e + jr z, LOOKUP + ld hl, SHIFT_TABLE + +LOOKUP: + ld c, a + ld b, 0 + add hl, bc + ld a, (hl) + or a ; fija flag Z segun corresponda + ret + +NEXT_ROW: + rlc b + inc d + ld a, d + cp 8 + jr nz, ROW_LOOP + +NO_KEY: + xor a + ret + + ; Orden de filas/columnas identico al de las tablas K-UNSHIFT/K-SHIFT + ; de la ROM original del ZX81 (fila 0: SHIFT,Z,X,C,V ... fila 7: + ; ENTER,L,K,J,H / SPACE,.,M,N,B), verificado contra el disassembly. + ; Las letras se decodifican en MINUSCULA, igual que el INKEY$ del + ; Spectrum en modo L (el por defecto): los programas de la epoca + ; comparan INKEY$ con "o","p","q","a","y","n"... y con mayusculas + ; no responderian (p.ej. examples/comecoquitos.bas). +UNSHIFT_TABLE: + DEFB 'z', 'x', 'c', 'v' + DEFB 'a', 's', 'd', 'f', 'g' + DEFB 'q', 'w', 'e', 'r', 't' + DEFB '1', '2', '3', '4', '5' + DEFB '0', '9', '8', '7', '6' + DEFB 'p', 'o', 'i', 'u', 'y' + DEFB 13, 'l', 'k', 'j', 'h' ; NEWLINE -> ENTER (ASCII 13) + DEFB ' ', '.', 'm', 'n', 'b' + +SHIFT_TABLE: + DEFB ':', ';', '?', '/' + DEFB 0, 0, 0, 0, 0 ; STOP, LPRINT, SLOW, FAST, LLIST + DEFB '"', 0, 0, 0, 0 ; "" (par de comillas), OR, STEP, <=, <> + DEFB 0, 0, 0, 0, 0 ; EDIT, AND, THEN, TO, cursor-izq + DEFB 12, 0, 0, 0, 0 ; RUBOUT (DEL=12), GRAPHICS, cursor der/arr/abj + DEFB '"', ')', '(', '$', 0 ; ", ), (, $, >= + DEFB 0, '=', '+', '-', 0 ; FUNCTION, =, +, -, ** + DEFB 0, ',', '>', '<', '*' ; £, ',', >, <, * + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/po_gr_1.asm b/src/lib/arch/zx81sd/runtime/po_gr_1.asm index 6f78aaf99..c1435cdd5 100644 --- a/src/lib/arch/zx81sd/runtime/po_gr_1.asm +++ b/src/lib/arch/zx81sd/runtime/po_gr_1.asm @@ -1,24 +1,20 @@ ; PO_GR_1 — Genera el patrón de bits para caracteres gráficos 128-143 -; Sustituye a PO_GR_1 EQU $0B38h (ROM Spectrum) +; Sustituye a PO-GR-1 ($0B38, ROM Spectrum). Mismo algoritmo que la ROM. ; ; Los caracteres gráficos Spectrum (CHR$ 128 a CHR$ 143) son bloques -; de pixels 2×2 en cuadrantes. El nibble bajo del código determina -; qué cuadrantes están encendidos (bit 0 = TL, 1 = TR, 2 = BL, 3 = BR). +; de 2×2 cuadrantes. El nibble bajo del código determina qué cuadrantes +; están encendidos, con el mapeo EXACTO de la ROM (PO-GR-2, $0B3E): ; -; Entrada: B = código de carácter (128-143), solo se usan bits 3-0 -; Salida: MEM0 (5 bytes) = patrón de 8 bytes del carácter generado -; HL = MEM0 (para que plot lo use directamente) -; Destruye: A, B, C, D, E +; bit 0 = cuadrante superior DERECHO → $0F en filas 0-3 +; bit 1 = cuadrante superior IZQUIERDO → $F0 en filas 0-3 +; bit 2 = cuadrante inferior DERECHO → $0F en filas 4-7 +; bit 3 = cuadrante inferior IZQUIERDO → $F0 en filas 4-7 ; -; Patrón generado: 4 líneas top + 4 líneas bottom -; top_byte = $FF si bit 0 (TL) || $F0 si bit 1 (TR) || combinación -; bot_byte = igual pero mirando bits 2 (BL) y 3 (BR) +; (CHR$(129) = ▝, CHR$(130) = ▘, CHR$(143) = bloque macizo.) ; -; Codificación exacta: -; bit 0 = cuadrante superior izquierdo → pixels $F0 en filas 0-3 -; bit 1 = cuadrante superior derecho → pixels $0F en filas 0-3 -; bit 2 = cuadrante inferior izquierdo → pixels $F0 en filas 4-7 -; bit 3 = cuadrante inferior derecho → pixels $0F en filas 4-7 +; Entrada: B = código de carácter (128-143), solo se usan bits 3-0 +; Salida: MEM0 (8 bytes) = patrón del carácter; HL = MEM0 +; Destruye: A, B, C #include once @@ -26,61 +22,34 @@ PO_GR_1: PROC + LOCAL PO_GR_HALF, PO_GR_FILL - ld a, b ; A = código gráfico (128-143) - and $0F ; quedarnos solo con los 4 bits de cuadrante - - ; Calcular byte superior (filas 0-3) - ld c, $00 - bit 0, a ; TL activo? - jr z, NO_TL - ld c, $F0 -NO_TL: - bit 1, a ; TR activo? - jr z, NO_TR - ld b, c - or $0F - ld c, a ld a, b -NO_TR: - ; C = byte superior - - ; Calcular byte inferior (filas 4-7) - ld d, a ; guardar A (bits cuadrante) - ld e, $00 - bit 2, d ; BL activo? - jr z, NO_BL - ld e, $F0 -NO_BL: - bit 3, d ; BR activo? - jr z, NO_BR - ld a, e - or $0F - ld e, a -NO_BR: - ; E = byte inferior - - ; Rellenar MEM0 con 8 bytes: 4x C, 4x E + and $0F + ld b, a ; B = bits de cuadrante ld hl, MEM0 - ld a, c - ld (hl), a - inc hl - ld (hl), a - inc hl - ld (hl), a - inc hl - ld (hl), a - inc hl - ld a, e - ld (hl), a - inc hl - ld (hl), a - inc hl + call PO_GR_HALF ; filas 0-3 (bits 0-1) + call PO_GR_HALF ; filas 4-7 (bits 2-3) + ld hl, MEM0 ; devuelve el puntero al patrón + ret + +; Construye media celda (4 filas iguales) a partir de los dos bits +; bajos de B, desplazándolos fuera. Igual que PO-GR-2 de la ROM. +PO_GR_HALF: + rr b ; bit par → carry + sbc a, a ; $00 o $FF + and $0F ; mitad derecha + ld c, a + rr b ; bit impar → carry + sbc a, a + and $F0 ; mitad izquierda + or c ; byte de la fila completo + ld c, 4 +PO_GR_FILL: ld (hl), a inc hl - ld (hl), a - - ld hl, MEM0 ; devolver puntero al patrón + dec c + jr nz, PO_GR_FILL ret ENDP From 5aa86b2ff26c9f1496fa7a17ecc12023db972b06 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 09:05:19 +0200 Subject: [PATCH 10/27] zx81sd: teclado con mayusculas/CAPS LOCK, fix scroll/heap, MSFS sobre bloque 7 (WIP) - backend/main.py: heap_size/heap_address se asignaban con ADD_IF_NOT_DEFINED, pero Z80Backend.init() ya los definia antes -> el heap se quedaba inline en vez de en $8100. Fix: asignacion directa tras super().init(). - keyscan.asm: rediseno completo. Pulsacion directa = minuscula, SHIFT+letra = mayuscula (antes solo devolvia minuscula), SHIFT+"2" = CAPS LOCK persistente, "." como tecla normal con los simbolos clasicos del ZX81 accesibles como tecla muerta desde INPUT() (ver input.bas). - input.bas: composicion de simbolos ZX81 (tecla muerta "."+tecla) y doble punto = punto literal sin combo. - print.asm: __SCROLL_SCR ya no cae en la ROM del Spectrum ($0DFE) - la implementacion por buffer es ahora la unica rama. - stdlib/scroll.bas: nuevo override zx81sd (llamaba a PIXEL-ADD $22AC de la ROM, sustituido por PIXEL_ADDR propio). - stdlib/cb/maskedsprites.bas: nuevo override que porta MSFS al mapeador de memoria (bloque 7) en vez de BANKM/$7FFD del Spectrum. Sigue en proceso, aun no funciona del todo bien. - mcu.bas: _McuStreamPrint admite controlar el salto de linea final (para listados tipo DIR sin salto extra). - fzx_fonts/: fuentes FZX de ejemplo para la libreria de fuentes. - examples/sd81/filesystem.bas: demo de McuCd/McuDirPrint/McuLoad. Co-Authored-By: Claude Sonnet 5 --- examples/sd81/filesystem.bas | 11 + src/arch/zx81sd/backend/main.py | 15 +- .../zx81sd/runtime/io/keyboard/keyscan.asm | 576 ++++--- src/lib/arch/zx81sd/runtime/print.asm | 10 +- .../arch/zx81sd/stdlib/cb/maskedsprites.bas | 1369 +++++++++++++++++ .../arch/zx81sd/stdlib/fzx_fonts/academy.fzx | Bin 0 -> 1254 bytes .../zx81sd/stdlib/fzx_fonts/belegost1.fzx | Bin 0 -> 1310 bytes .../zx81sd/stdlib/fzx_fonts/belegost2.fzx | Bin 0 -> 1358 bytes .../arch/zx81sd/stdlib/fzx_fonts/bigbold.fzx | Bin 0 -> 1450 bytes .../arch/zx81sd/stdlib/fzx_fonts/cobra.fzx | Bin 0 -> 1332 bytes .../arch/zx81sd/stdlib/fzx_fonts/crash.fzx | Bin 0 -> 1208 bytes .../arch/zx81sd/stdlib/fzx_fonts/d_o_c.fzx | Bin 0 -> 1169 bytes .../arch/zx81sd/stdlib/fzx_fonts/eclipse.fzx | Bin 0 -> 1163 bytes .../arch/zx81sd/stdlib/fzx_fonts/extra.fzx | Bin 0 -> 1507 bytes .../arch/zx81sd/stdlib/fzx_fonts/hijack.fzx | Bin 0 -> 1270 bytes .../arch/zx81sd/stdlib/fzx_fonts/italika.fzx | Bin 0 -> 1234 bytes .../arch/zx81sd/stdlib/fzx_fonts/just6.fzx | Bin 0 -> 1241 bytes .../zx81sd/stdlib/fzx_fonts/locomotion.fzx | Bin 0 -> 1270 bytes .../arch/zx81sd/stdlib/fzx_fonts/midnight.fzx | Bin 0 -> 1236 bytes .../zx81sd/stdlib/fzx_fonts/moonalert.fzx | Bin 0 -> 1167 bytes .../arch/zx81sd/stdlib/fzx_fonts/nether.fzx | Bin 0 -> 1222 bytes .../arch/zx81sd/stdlib/fzx_fonts/neverend.fzx | Bin 0 -> 1266 bytes .../arch/zx81sd/stdlib/fzx_fonts/roman.fzx | Bin 0 -> 1197 bytes .../arch/zx81sd/stdlib/fzx_fonts/script.fzx | Bin 0 -> 1230 bytes .../arch/zx81sd/stdlib/fzx_fonts/script2.fzx | Bin 0 -> 1295 bytes .../arch/zx81sd/stdlib/fzx_fonts/standard.fzx | Bin 0 -> 1255 bytes .../arch/zx81sd/stdlib/fzx_fonts/tomahawk.fzx | Bin 0 -> 1235 bytes .../zx81sd/stdlib/fzx_fonts/ultrabold.fzx | Bin 0 -> 1462 bytes .../zx81sd/stdlib/fzx_fonts/upcasebold.fzx | Bin 0 -> 1367 bytes .../arch/zx81sd/stdlib/fzx_fonts/wildvest.fzx | Bin 0 -> 1212 bytes .../arch/zx81sd/stdlib/fzx_fonts/winter.fzx | Bin 0 -> 1240 bytes src/lib/arch/zx81sd/stdlib/input.bas | 60 + src/lib/arch/zx81sd/stdlib/mcu.bas | 19 +- src/lib/arch/zx81sd/stdlib/scroll.bas | 1043 +++++++++++++ 34 files changed, 2910 insertions(+), 193 deletions(-) create mode 100644 examples/sd81/filesystem.bas create mode 100644 src/lib/arch/zx81sd/stdlib/cb/maskedsprites.bas create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/academy.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/belegost1.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/belegost2.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/bigbold.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/cobra.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/crash.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/d_o_c.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/eclipse.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/extra.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/hijack.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/italika.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/just6.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/locomotion.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/midnight.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/moonalert.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/nether.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/neverend.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/roman.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/script.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/script2.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/standard.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/tomahawk.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/ultrabold.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/upcasebold.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/wildvest.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/fzx_fonts/winter.fzx create mode 100644 src/lib/arch/zx81sd/stdlib/scroll.bas diff --git a/examples/sd81/filesystem.bas b/examples/sd81/filesystem.bas new file mode 100644 index 000000000..5adce9089 --- /dev/null +++ b/examples/sd81/filesystem.bas @@ -0,0 +1,11 @@ +#include +#include +dim version as ubyte +version = McuVersion() +print "SD81 OS version: ";version +print "Current dir: "; McuPwd() +if McuCd("/")=0 then print "Change to root dir" else print "error" +if McuCd("/test")=0 then print "Change to test dir" else print "error" +McuDirPrint("/test/*.*") +McuLoad("/DEMO.WAV",0) +input a$ \ No newline at end of file diff --git a/src/arch/zx81sd/backend/main.py b/src/arch/zx81sd/backend/main.py index df868e78d..c4e1378af 100644 --- a/src/arch/zx81sd/backend/main.py +++ b/src/arch/zx81sd/backend/main.py @@ -68,10 +68,17 @@ def init(self): super().init() OPTIONS(Action.ADD_IF_NOT_DEFINED, name="org", type=int, default=_ORG) - OPTIONS(Action.ADD_IF_NOT_DEFINED, name="heap_size", type=int, - default=_HEAP_SIZE, ignore_none=True) - OPTIONS(Action.ADD_IF_NOT_DEFINED, name="heap_address", type=int, - default=_HEAP_ADDR, ignore_none=False) + + # El backend Z80 generico (super().init(), justo arriba) ya registra + # "heap_size" (4768) y "heap_address" (None) con ADD_IF_NOT_DEFINED, + # asi que un ADD_IF_NOT_DEFINED aqui seria un no-op y el heap acababa + # reservado inline (DEFS) con 4768 bytes dentro de la zona ejecutable. + # Se asigna directamente para que el heap viva en la zona de datos + # $8100-$BFFF; los flags --heap-address/-H de la CLI se aplican + # despues y siguen pudiendo sobreescribir estos valores. + OPTIONS.heap_size = _HEAP_SIZE + OPTIONS.heap_address = _HEAP_ADDR + OPTIONS(Action.ADD_IF_NOT_DEFINED, name="heap_start_label", type=str, default=f"{NAMESPACE}.ZXBASIC_MEM_HEAP") OPTIONS(Action.ADD_IF_NOT_DEFINED, name="heap_size_label", type=str, diff --git a/src/lib/arch/zx81sd/runtime/io/keyboard/keyscan.asm b/src/lib/arch/zx81sd/runtime/io/keyboard/keyscan.asm index 2c5809320..8bf0a78cd 100644 --- a/src/lib/arch/zx81sd/runtime/io/keyboard/keyscan.asm +++ b/src/lib/arch/zx81sd/runtime/io/keyboard/keyscan.asm @@ -1,179 +1,397 @@ -; --------------------------------------------------------------------------- -; keyscan.asm — Escaneo directo de la matriz de teclado del ZX81 -; -; No hay ROM mapeada en tiempo de ejecucion (el bloque 0 lo ocupa por -; completo nuestro binario compilado), asi que no se puede llamar a la -; rutina KEYBOARD/DECODE de la ROM original. El hardware del teclado, en -; cambio, es el mismo ZX81 de siempre (el SD81 Booster no lo toca), asi -; que se reimplementa aqui el escaneo fisico (puerto $FEFE, 8 filas -; seleccionadas rotando el registro alto del puerto) y una decodificacion -; propia a ASCII. -; -; IMPORTANTE: la ROM del ZX81 decodifica las teclas a SU PROPIO charset -; (con codigos de token para palabras clave de BASIC), no a ASCII. El -; runtime de zx81sd usa el charset Spectrum/ASCII para imprimir (ver -; charset.asm, specfont.bin), asi que la tabla de decodificacion de abajo -; traduce directamente cada tecla a su codigo ASCII, ignorando las teclas -; que en el ZX81 producen tokens de BASIC sin equivalente ASCII simple -; (STOP, AND, OR, THEN, TO, STEP, <=, >=, <>, **, EDIT, GRAPHICS, -; FUNCTION, cursores, LPRINT, LLIST, SLOW, FAST). La tecla RUBOUT -; (SHIFT+0) se traduce a ASCII 12, y ENTER (NEWLINE) a ASCII 13, para -; mantener compatibilidad con la convencion ya usada por stdlib/input.bas. -; --------------------------------------------------------------------------- - - push namespace core - -; --------------------------------------------------------------------------- -; __ZX81SD_KEYSCAN -; -; Escanea las 8 filas de la matriz y decodifica la primera tecla -; encontrada (si hay varias pulsadas a la vez, solo se detecta una). -; -; Devuelve: -; A = codigo ASCII de la tecla pulsada, o 0 si no hay ninguna pulsada, -; o si la tecla no tiene equivalente ASCII (ver nota arriba), o si -; solo esta pulsada la tecla SHIFT sola. -; Flag Z activo si A = 0 -; -; Registros modificados: AF, BC, DE, HL -; --------------------------------------------------------------------------- -__ZX81SD_KEYSCAN: - PROC - LOCAL ROW_LOOP - LOCAL NEXT_ROW - LOCAL FIND_COL - LOCAL GOT_COL0 - LOCAL GOT_COL1 - LOCAL GOT_COL2 - LOCAL GOT_COL3 - LOCAL GOT_COL4 - LOCAL GOT_KEY - LOCAL LOOKUP - LOCAL NO_KEY - LOCAL UNSHIFT_TABLE - LOCAL SHIFT_TABLE - - ; D = indice de fila actual (0-7), E = flag SHIFT pulsado (0/1) - ld d, 0 - ld e, 0 - ld b, $FE ; B = mitad alta del puerto ($FEFE, $FDFE, ... $7FFE) - -ROW_LOOP: - ld c, $FE - in a, (c) ; lee la fila; bits 0-4 = columnas (0 = pulsada) - and $1F - cp $1F - jr z, NEXT_ROW ; ninguna tecla pulsada en esta fila - - ld l, a ; L = mapa de bits de columnas pulsadas - - ld a, d - or a - jr nz, FIND_COL - - ; Fila 0: la columna 0 es la tecla SHIFT (no genera caracter propio) - bit 0, l - jr nz, FIND_COL ; SHIFT no pulsado, comprobar columnas normalmente - ld e, 1 ; SHIFT pulsado - set 0, l ; descartar ese bit para no confundirlo con datos - ld a, l - cp $1F - jr z, NEXT_ROW ; en esta fila solo estaba pulsado SHIFT - -FIND_COL: - ; Bucle desenrollado a proposito: usar CP para comparar el contador - ; de columna sobreescribia A (el mapa de bits que se iba rotando) - ; antes de que la siguiente vuelta pudiera comprobarlo, con lo que - ; solo la columna 0 se detectaba bien. Sin bucle no hay ese riesgo. - ld a, l - rrca - jr nc, GOT_COL0 - rrca - jr nc, GOT_COL1 - rrca - jr nc, GOT_COL2 - rrca - jr nc, GOT_COL3 - rrca - jr nc, GOT_COL4 - jr NEXT_ROW ; no deberia ocurrir (ya se comprobo cp $1F antes) - -GOT_COL0: - ld c, 0 - jr GOT_KEY -GOT_COL1: - ld c, 1 - jr GOT_KEY -GOT_COL2: - ld c, 2 - jr GOT_KEY -GOT_COL3: - ld c, 3 - jr GOT_KEY -GOT_COL4: - ld c, 4 - -GOT_KEY: - ; indice de tabla = fila*5 + columna - 1 - ; (la fila 0 solo aporta 4 teclas: columnas 1-4, de ahi el -1) - ld a, d - add a, a - add a, a - add a, d ; A = fila*5 - add a, c ; A = fila*5 + columna - dec a ; A = indice (0-38) - - ld hl, UNSHIFT_TABLE - bit 0, e - jr z, LOOKUP - ld hl, SHIFT_TABLE - -LOOKUP: - ld c, a - ld b, 0 - add hl, bc - ld a, (hl) - or a ; fija flag Z segun corresponda - ret - -NEXT_ROW: - rlc b - inc d - ld a, d - cp 8 - jr nz, ROW_LOOP - -NO_KEY: - xor a - ret - - ; Orden de filas/columnas identico al de las tablas K-UNSHIFT/K-SHIFT - ; de la ROM original del ZX81 (fila 0: SHIFT,Z,X,C,V ... fila 7: - ; ENTER,L,K,J,H / SPACE,.,M,N,B), verificado contra el disassembly. - ; Las letras se decodifican en MINUSCULA, igual que el INKEY$ del - ; Spectrum en modo L (el por defecto): los programas de la epoca - ; comparan INKEY$ con "o","p","q","a","y","n"... y con mayusculas - ; no responderian (p.ej. examples/comecoquitos.bas). -UNSHIFT_TABLE: - DEFB 'z', 'x', 'c', 'v' - DEFB 'a', 's', 'd', 'f', 'g' - DEFB 'q', 'w', 'e', 'r', 't' - DEFB '1', '2', '3', '4', '5' - DEFB '0', '9', '8', '7', '6' - DEFB 'p', 'o', 'i', 'u', 'y' - DEFB 13, 'l', 'k', 'j', 'h' ; NEWLINE -> ENTER (ASCII 13) - DEFB ' ', '.', 'm', 'n', 'b' - -SHIFT_TABLE: - DEFB ':', ';', '?', '/' - DEFB 0, 0, 0, 0, 0 ; STOP, LPRINT, SLOW, FAST, LLIST - DEFB '"', 0, 0, 0, 0 ; "" (par de comillas), OR, STEP, <=, <> - DEFB 0, 0, 0, 0, 0 ; EDIT, AND, THEN, TO, cursor-izq - DEFB 12, 0, 0, 0, 0 ; RUBOUT (DEL=12), GRAPHICS, cursor der/arr/abj - DEFB '"', ')', '(', '$', 0 ; ", ), (, $, >= - DEFB 0, '=', '+', '-', 0 ; FUNCTION, =, +, -, ** - DEFB 0, ',', '>', '<', '*' ; £, ',', >, <, * - - ENDP - - pop namespace +; --------------------------------------------------------------------------- +; keyscan.asm — Escaneo directo de la matriz de teclado del ZX81 +; +; No hay ROM mapeada en tiempo de ejecucion (el bloque 0 lo ocupa por +; completo nuestro binario compilado), asi que no se puede llamar a la +; rutina KEYBOARD/DECODE de la ROM original. El hardware del teclado, en +; cambio, es el mismo ZX81 de siempre (el SD81 Booster no lo toca), asi +; que se reimplementa aqui el escaneo fisico (puerto $FEFE, 8 filas +; seleccionadas rotando el registro alto del puerto) y una decodificacion +; propia a ASCII. +; +; ESQUEMA DE TECLAS (2026-07-04, revisado tras probar en el emulador) +; -------------------------------------------------------------------- +; El teclado fisico del ZX81 no tiene mayuscula/minuscula por tecla (SHIFT +; da simbolos, no la version en mayuscula de la letra) — a diferencia del +; Spectrum, donde SHIFT+letra si da la mayuscula de esa letra via la ROM +; (K-DECODE en modo L). Para poder imprimir el juego de caracteres +; completo sin depender de teclado externo: +; +; Sin modificador -> minuscula (a, s, d... como hasta ahora) +; SHIFT + letra -> MAYUSCULA de esa letra +; SHIFT + "2" -> conmuta CAPS LOCK (persistente; no imprime nada) +; CAPS LOCK activo -> minuscula pasa a mayuscula (SHIFT sigue dando +; mayuscula igual, no hay interaccion: es un OR) +; "." (con o sin SHIFT) -> "." o "," exactamente igual que en el ZX81 +; real (SHIFT+"." = ",") +; +; SHIFT es comodo de mantener pulsado con una mano mientras se pulsa la +; letra con la otra (aqui __ZX81SD_KEYSCAN SI detecta la simultaneidad +; real, con dos lecturas de puerto dedicadas para SHIFT y una tercera +; tecla). Pero un primer diseno que anadia un segundo modificador de +; simbolo con "." (pulsar "."+tecla a la vez para sacar el simbolo +; impreso en el teclado del ZX81) resulto impracticable de teclear: al +; usarse desde INPUT, la rutina de lectura ya compromete la tecla "." +; en cuanto se detecta pulsada sola, sin dar tiempo a que la segunda +; tecla llegue a la vez (no son dedos que se muevan en paralelo como +; SHIFT+letra, sino una pulsacion despues de otra). Por eso ese segundo +; modificador se ha movido a stdlib/input.bas, como una "tecla muerta" +; a nivel de composicion de caracteres (se pulsa "." y LUEGO la tecla +; del simbolo, secuencialmente, sin necesidad de mantenerlas a la vez): +; ver __ZX81SD_SYMBOL_FOR mas abajo, que da el mapeo tecla->simbolo que +; usa esa logica. +; +; IMPORTANTE: la ROM del ZX81 decodifica las teclas a SU PROPIO charset +; (con codigos de token para palabras clave de BASIC), no a ASCII. El +; runtime de zx81sd usa el charset Spectrum/ASCII para imprimir (ver +; charset.asm, specfont.bin), asi que las tablas de decodificacion de +; abajo traducen directamente cada tecla a su codigo ASCII, ignorando las +; teclas que en el ZX81 producen tokens de BASIC sin equivalente ASCII +; simple (STOP, AND, OR, THEN, TO, STEP, <=, >=, <>, **, EDIT, GRAPHICS, +; FUNCTION, cursores, LPRINT, LLIST, SLOW, FAST). La tecla RUBOUT +; (SHIFT+0) se traduce a ASCII 12, y ENTER (NEWLINE) a ASCII 13, para +; mantener compatibilidad con la convencion ya usada por stdlib/input.bas. +; --------------------------------------------------------------------------- + + push namespace core + +; --- Tablas de decodificacion, en ambito de fichero (no LOCAL a ningun +; PROC): las usa __ZX81SD_KEYSCAN y tambien __ZX81SD_SYMBOL_FOR. --- +; +; Orden de filas/columnas identico al de las tablas K-UNSHIFT/K-SHIFT de +; la ROM original del ZX81 (fila 0: SHIFT,Z,X,C,V ... fila 7: ENTER,L,K, +; J,H / SPACE,.,M,N,B), verificado contra el disassembly. +__ZX81SD_UNSHIFT_TABLE: + DEFB 'z', 'x', 'c', 'v' + DEFB 'a', 's', 'd', 'f', 'g' + DEFB 'q', 'w', 'e', 'r', 't' + DEFB '1', '2', '3', '4', '5' + DEFB '0', '9', '8', '7', '6' + DEFB 'p', 'o', 'i', 'u', 'y' + DEFB 13, 'l', 'k', 'j', 'h' ; NEWLINE -> ENTER (ASCII 13) + DEFB ' ', '.', 'm', 'n', 'b' + +; __ZX81SD_SYMBOL_TABLE — simbolos impresos en el teclado del ZX81 bajo +; SHIFT. Alineada posicion a posicion con __ZX81SD_UNSHIFT_TABLE (mismo +; indice = misma tecla fisica); la usa __ZX81SD_SYMBOL_FOR para el modo +; de composicion "." + tecla de stdlib/input.bas. +__ZX81SD_SYMBOL_TABLE: + DEFB ':', ';', '?', '/' + DEFB 0, 0, 0, 0, 0 ; STOP, LPRINT, SLOW, FAST, LLIST + DEFB '"', 0, 0, 0, 0 ; "" (par de comillas), OR, STEP, <=, <> + DEFB 0, 0, 0, 0, 0 ; EDIT, [CAPS LOCK], THEN, TO, cursor-izq + DEFB 12, 0, 0, 0, 0 ; RUBOUT (DEL=12), GRAPHICS, cursor der/arr/abj + DEFB '"', ')', '(', '$', 0 ; ", ), (, $, >= + DEFB 0, '=', '+', '-', 0 ; FUNCTION, =, +, -, ** + DEFB 0, ',', '>', '<', '*' ; £, ',', >, <, * + +; __ZX81SD_CAPS_TABLE — igual que __ZX81SD_SYMBOL_TABLE, pero con la +; MAYUSCULA en cada posicion que en UNSHIFT_TABLE es una letra (para +; SHIFT+letra, y para el modo CAPS LOCK persistente). Las posiciones que +; no son letra se dejan igual que en SYMBOL_TABLE (digitos, ENTER, +; SPACE, RUBOUT...). +__ZX81SD_CAPS_TABLE: + DEFB 'Z', 'X', 'C', 'V' + DEFB 'A', 'S', 'D', 'F', 'G' + DEFB 'Q', 'W', 'E', 'R', 'T' + DEFB 0, 0, 0, 0, 0 ; digitos 1-5 (el "2" se intercepta antes) + DEFB 12, 0, 0, 0, 0 ; digitos 0,9,8,7,6 (RUBOUT en el "0") + DEFB 'P', 'O', 'I', 'U', 'Y' + DEFB 0, 'L', 'K', 'J', 'H' ; ENTER sin cambio, L/K/J/H en mayuscula + DEFB 0, ',', 'M', 'N', 'B' ; SPACE sin cambio, "." no se alcanza aqui + +; --- Estado persistente del teclado (sobrevive entre llamadas) --- +__ZX81SD_KBD_CAPSLOCK: DEFB 0 ; 1 = CAPS LOCK activo +__ZX81SD_KBD_CAPS_EDGE: DEFB 0 ; deteccion de flanco para el combo SHIFT+"2" + +; --- Estado transitorio (se recalcula en cada llamada a __ZX81SD_KEYSCAN) --- +__ZX81SD_KBD_SHIFT: DEFB 0 +__ZX81SD_KBD_OTHER_VALID: DEFB 0 +__ZX81SD_KBD_OTHER_IDX: DEFB 0 + +; --------------------------------------------------------------------------- +; __ZX81SD_KEYSCAN +; +; Escanea el teclado y decodifica la tecla pulsada segun el esquema de +; arriba (SHIFT como modificador de mayuscula, CAPS LOCK persistente). +; +; Devuelve: +; A = codigo ASCII de la tecla pulsada, o 0 si no hay ninguna pulsada, +; si la combinacion no tiene equivalente ASCII, si solo esta +; pulsado SHIFT solo, o si se acaba de conmutar el CAPS LOCK +; (SHIFT+"2"). +; Flag Z activo si A = 0 +; +; Registros modificados: AF, BC, DE, HL +; --------------------------------------------------------------------------- +__ZX81SD_KEYSCAN: + PROC + LOCAL FIND_OTHER + LOCAL ROW_LOOP + LOCAL NEXT_ROW + LOCAL FIND_COL + LOCAL GOT_COL0 + LOCAL GOT_COL1 + LOCAL GOT_COL2 + LOCAL GOT_COL3 + LOCAL GOT_COL4 + LOCAL GOT_KEY + LOCAL OTHER_FOUND + LOCAL OTHER_NONE + LOCAL NOT_ROW0 + + LOCAL DECIDE + LOCAL HAVE_SHIFT + LOCAL SHIFT_ALONE + LOCAL CHECK_CAPS_KEY + LOCAL DO_CAPS_TOGGLE + LOCAL CAPS_EDGE_DONE + LOCAL NO_SHIFT + LOCAL NO_KEY + LOCAL USE_UNSHIFT + LOCAL USE_CAPS_FOR_PLAIN + LOCAL LOOKUP_OTHER_IN_HL + LOCAL RESET_CAPS_EDGE + + LOCAL SHIFT_BIT_SET + + ; --- 1. Leer SHIFT (fila 0, columna 0) con una lectura dedicada --- + ld bc, $FEFE + in a, (c) + and $01 + ld a, 1 + jr z, SHIFT_BIT_SET + xor a +SHIFT_BIT_SET: + ld (__ZX81SD_KBD_SHIFT), a + + ; --- 2. Buscar otra tecla pulsada (excluyendo SHIFT) --- + xor a + ld (__ZX81SD_KBD_OTHER_VALID), a + call FIND_OTHER ; carry activo = encontrada (indice en A) + jr nc, DECIDE + ld (__ZX81SD_KBD_OTHER_IDX), a + ld a, 1 + ld (__ZX81SD_KBD_OTHER_VALID), a + +DECIDE: + ld a, (__ZX81SD_KBD_SHIFT) + or a + jr nz, HAVE_SHIFT + jr NO_SHIFT + +HAVE_SHIFT: + ld a, (__ZX81SD_KBD_OTHER_VALID) + or a + jr nz, CHECK_CAPS_KEY + +SHIFT_ALONE: + ; Solo SHIFT: sin caracter, y se reinicia la deteccion de flanco del + ; combo SHIFT+"2" (no puede haber toggle sin la tecla "2" presente). + call RESET_CAPS_EDGE + xor a + ret + +CHECK_CAPS_KEY: + ld a, (__ZX81SD_KBD_OTHER_IDX) + cp 15 ; indice de la tecla "2" (fila 3, columna 1) + jr z, DO_CAPS_TOGGLE + + ; SHIFT + letra (o cualquier otra tecla, salvo "2"): __ZX81SD_CAPS_TABLE + ; (mayuscula en las posiciones de letra; en el resto, igual que + ; __ZX81SD_SYMBOL_TABLE — p.ej. SHIFT+"." sigue dando ","). + call RESET_CAPS_EDGE + ld hl, __ZX81SD_CAPS_TABLE + jr LOOKUP_OTHER_IN_HL + +DO_CAPS_TOGGLE: + ; Conmuta CAPS LOCK solo en el flanco de subida del combo (para no + ; conmutarlo en cada llamada mientras se mantiene pulsado). + ld a, (__ZX81SD_KBD_CAPS_EDGE) + or a + jr nz, CAPS_EDGE_DONE + ld a, 1 + ld (__ZX81SD_KBD_CAPS_EDGE), a + ld a, (__ZX81SD_KBD_CAPSLOCK) + xor 1 + ld (__ZX81SD_KBD_CAPSLOCK), a +CAPS_EDGE_DONE: + xor a ; SHIFT+"2" es mudo: nunca devuelve caracter + ret + +NO_SHIFT: + call RESET_CAPS_EDGE + + ld a, (__ZX81SD_KBD_OTHER_VALID) + or a + jr z, NO_KEY + + ; Sin SHIFT: __ZX81SD_UNSHIFT_TABLE (minuscula), salvo que CAPS LOCK + ; este activo, en cuyo caso se usa __ZX81SD_CAPS_TABLE (mayuscula en + ; las letras). "." cae aqui igual que cualquier otra tecla: siempre + ; devuelve "." (o "," si SHIFT, ya cubierto arriba). + ld a, (__ZX81SD_KBD_CAPSLOCK) + or a + jr z, USE_UNSHIFT + jr USE_CAPS_FOR_PLAIN + +USE_UNSHIFT: + ld hl, __ZX81SD_UNSHIFT_TABLE + jr LOOKUP_OTHER_IN_HL + +USE_CAPS_FOR_PLAIN: + ld hl, __ZX81SD_CAPS_TABLE + +LOOKUP_OTHER_IN_HL: + ld a, (__ZX81SD_KBD_OTHER_IDX) + ld e, a + ld d, 0 + add hl, de + ld a, (hl) + or a + ret + +NO_KEY: + xor a + ret + +RESET_CAPS_EDGE: + xor a + ld (__ZX81SD_KBD_CAPS_EDGE), a + ret + +; --------------------------------------------------------------------------- +; FIND_OTHER — busca una tecla pulsada distinta de SHIFT (fila 0, col 0). +; Devuelve acarreo INACTIVO si no encuentra ninguna; si encuentra una, +; acarreo ACTIVO y A = indice (0-38, formula fila*5+columna-1). Se usa el +; acarreo (no Z) porque el indice 0 (tecla Z) es un resultado valido con +; A=0, que haria Z activo por error si se usara ese flag para +; "encontrada/no encontrada". +; Registros modificados: AF, BC, DE, HL. +; --------------------------------------------------------------------------- +FIND_OTHER: + ld d, 0 ; D = indice de fila actual (0-7) + ld b, $FE ; B = mitad alta del puerto ($FEFE...$7FFE) + +ROW_LOOP: + ld c, $FE + in a, (c) ; lee la fila; bits 0-4 = columnas (0 = pulsada) + and $1F + + ld l, a ; L = mapa de bits de columnas pulsadas + + ld a, d + or a + jr nz, NOT_ROW0 + set 0, l ; fila 0: descartar SHIFT (columna 0) +NOT_ROW0: + ld a, l + cp $1F + jr z, NEXT_ROW ; nada mas pulsado en esta fila + +FIND_COL: + ; Bucle desenrollado a proposito: usar CP para comparar el contador + ; de columna sobreescribia A (el mapa de bits que se iba rotando) + ; antes de que la siguiente vuelta pudiera comprobarlo, con lo que + ; solo la columna 0 se detectaba bien. Sin bucle no hay ese riesgo. + ld a, l + rrca + jr nc, GOT_COL0 + rrca + jr nc, GOT_COL1 + rrca + jr nc, GOT_COL2 + rrca + jr nc, GOT_COL3 + rrca + jr nc, GOT_COL4 + jr NEXT_ROW ; no deberia ocurrir (ya se comprobo cp $1F antes) + +GOT_COL0: + ld c, 0 + jr GOT_KEY +GOT_COL1: + ld c, 1 + jr GOT_KEY +GOT_COL2: + ld c, 2 + jr GOT_KEY +GOT_COL3: + ld c, 3 + jr GOT_KEY +GOT_COL4: + ld c, 4 + +GOT_KEY: + ; indice de tabla = fila*5 + columna - 1 + ; (la fila 0 solo aporta 4 teclas: columnas 1-4, de ahi el -1) + ld a, d + add a, a + add a, a + add a, d ; A = fila*5 + add a, c ; A = fila*5 + columna + dec a ; A = indice (0-38) + jr OTHER_FOUND + +NEXT_ROW: + rlc b + inc d + ld a, d + cp 8 + jr nz, ROW_LOOP + +OTHER_NONE: + xor a + ret ; carry inactivo (xor siempre lo limpia) + +OTHER_FOUND: + scf ; carry activo; A ya tiene el indice (GOT_KEY) + ret + + ENDP + +; --------------------------------------------------------------------------- +; __ZX81SD_SYMBOL_FOR — dado un caracter ASCII (el que devolveria una +; pulsacion normal sin modificadores, es decir uno de los que aparecen en +; __ZX81SD_UNSHIFT_TABLE), devuelve el simbolo del ZX81 que le +; corresponde bajo el antiguo modificador SHIFT del ZX81 (o 0 si esa +; tecla no tiene simbolo). La usa stdlib/input.bas para componer simbolos +; como una "tecla muerta": el usuario pulsa "." y LUEGO esta tecla, sin +; necesitar mantenerlas pulsadas a la vez (ver cabecera del fichero). +; +; Entrada: A = caracter ASCII de la segunda tecla +; Salida: A = simbolo del ZX81, o 0 si esa tecla no tiene simbolo +; Registros modificados: AF, BC, DE, HL +; --------------------------------------------------------------------------- +__ZX81SD_SYMBOL_FOR: + PROC + LOCAL SEARCH_LOOP + LOCAL FOUND + LOCAL NOT_FOUND + + ld c, a ; C = caracter buscado + ld hl, __ZX81SD_UNSHIFT_TABLE + ld de, __ZX81SD_SYMBOL_TABLE + ld b, 39 ; numero de entradas de ambas tablas + +SEARCH_LOOP: + ld a, (hl) + cp c + jr z, FOUND + inc hl + inc de + djnz SEARCH_LOOP + jr NOT_FOUND + +FOUND: + ld a, (de) + or a + ret + +NOT_FOUND: + xor a + ret + + ENDP + + pop namespace diff --git a/src/lib/arch/zx81sd/runtime/print.asm b/src/lib/arch/zx81sd/runtime/print.asm index 97c589c7a..1bb7a0285 100644 --- a/src/lib/arch/zx81sd/runtime/print.asm +++ b/src/lib/arch/zx81sd/runtime/print.asm @@ -418,7 +418,12 @@ __ITALIC: #ifndef __ZXB_DISABLE_SCROLL LOCAL __SCROLL_SCR -# ifdef __ZXB_ENABLE_BUFFER_SCROLL +; En zx81sd el scroll se hace SIEMPRE con la implementacion por buffer: +; la variante del zx48k ("__SCROLL_SCR EQU 0DFEh") delega en la rutina +; CL-SC-ALL de la ROM del Spectrum, que aqui no esta mapeada — ese CALL +; ejecutaria codigo arbitrario del programa (bug cazado con flights.bas: +; el primer PRINT que desbordaba la pantalla saltaba a la linea BASIC +; que casualmente ocupara $0DFE). __SCROLL_SCR: ;; Scrolls screen and attrs 1 row up ld de, (SCREEN_ADDR) ld b, 3 @@ -487,9 +492,6 @@ __SCROLL_SCR: ;; Scrolls screen and attrs 1 row up ld bc, 31 ldir ret -# else -__SCROLL_SCR EQU 0DFEh ; Use ROM SCROLL -# endif #endif diff --git a/src/lib/arch/zx81sd/stdlib/cb/maskedsprites.bas b/src/lib/arch/zx81sd/stdlib/cb/maskedsprites.bas new file mode 100644 index 000000000..6e2feff2c --- /dev/null +++ b/src/lib/arch/zx81sd/stdlib/cb/maskedsprites.bas @@ -0,0 +1,1369 @@ +' ---------------------------------------------------------------- +' This file is released under the MIT License +' +' Copyright (C) 2026 Conrado Badenas +' Ideas taken from +' https://github.com/boriel-basic/zxbasic/blob/main/src/lib/arch/zx48k/stdlib/memorybank.bas +' by Juan Segura (a.k.a. Duefectu), +' https://github.com/oisee/antique-toy/blob/main/chapters/ch16-sprites/draft.md +' by Alice Vinogradova (a.k.a. oisee), and +' https://youtu.be/nBHXtI1Y-xU?t=434 and https://youtu.be/-AUmmzDiGlE?t=434 +' by Benjamín (a.k.a. RetrobenSoft) +' +' Print Masked (AND+OR) Sprites, version 2026.04.05 +' +' Version zx81sd (2026-07-04): la version de zx48k asume paginacion de +' memoria al estilo Spectrum 128K (banco visible en $c000-$ffff via +' puerto $7FFDh, sysvar BANKM en $5B5C) para el subsistema MSFS (Masked +' Sprites File System, un almacen de imagenes de sprite en RAM de +' sobra). Nada de eso existe en zx81sd. Se sustituye por el mapeador de +' memoria propio (puerto $E7h) sobre el BLOQUE 7 ($E000-$FFFF), que en +' nuestro mapa de memoria ya esta reservado para "banking de datos +' (mapas, sprites...)" -- justo este caso de uso. +' +' Las funciones de MSFS (RegisterSpriteImageInMSFS, FindFirstUnusedBlock +' InMSFS, etc.) son agnosticas de banco/direccion: solo llaman a +' GetBankPreservingRegs/SetBankPreservingINTs y leen/escriben la +' variable BASIC MaskedSpritesFileSystemStart. Reescribiendo esas DOS +' primitivas (y el calculo de esa direccion en InitMaskedSpritesFileSystem, +' mas abajo, que en el original asumia RAM plana Spectrum hasta $FFFF) +' el resto del fichero se copia sin tocar una sola linea. +' +' CheckMemoryPaging() aqui solo influye, en el ejemplo, en si se usa +' pantalla visible doble (bancos 5/7 del Spectrum) -- zx81sd tiene un +' unico framebuffer fisico (bloque 6, SCREEN_ADDR fijo en $C000) y esa +' funcionalidad no esta cubierta aqui (ver nota en SetVisibleScreen mas +' abajo). Las funciones de MSFS NO consultan CheckMemoryPaging() para +' decidir si usar el banco -- lo hacen incondicionalmente -- asi que +' devolver 0 (honesto: no hay doble pantalla visible) no le afecta en +' nada a que MSFS funcione. +' +' RESIDENCIA: desde InitMaskedSpritesFileSystem(), la pagina de MSFS se +' queda mapeada de forma PERMANENTE en el bloque 7 (SetBankPreservingINTs +' con valor <> 7 solo anota el numero, no desmapea) -- las rutinas de +' dibujo del bucle principal acceden a la MSFS sin envolver con +' Get/SetBank y necesitan verla siempre. Si un programa usa el bloque 7 +' para su propio banking de datos, debe remapear su pagina el mismo y +' llamar a SetBankPreservingINTs(7) antes de volver a usar MSFS. +' ---------------------------------------------------------------- + +#ifndef __CB_MASKEDSPRITES__ + +REM Avoid recursive / multiple inclusion + +#define __CB_MASKEDSPRITES__ + +#include +#include +#include + + +' ---------------------------------------------------------------- +' Banco de MSFS sobre el mapeador de zx81sd (puerto $E7h, bloque 7) +' +' MaskedSprites_MSFS_Page es la pagina SD81 dedicada a guardar la MSFS. +' Debe evitar las paginas 8-13 (codigo/heap, fijas por el bootstrap, ver +' src/arch/zx81sd/backend/main.py _PAGE_MAP) y 63 (pagina "libre" por +' convencion del cargador SD81: split_sd81.py deja siempre el bloque 7 +' apuntando a la pagina 63 tras cargar, antes de saltar al programa). +' Redefinible con #define ANTES de este #include si el programa ya usa +' la pagina 20 para otra cosa (no hay todavia un asignador global de +' paginas en el proyecto). +' ---------------------------------------------------------------- +#ifndef MaskedSprites_MSFS_Page + #define MaskedSprites_MSFS_Page 20 +#endif + +' Byte de estado puro en ASM (no DIM): un DIM normal se eliminaria por +' "codigo muerto" al no tener ninguna referencia desde BASIC (solo se +' toca desde las dos rutinas ASM de abajo). +ASM +_ZX81SD_MSFS_Bank: + DEFB 0 +END ASM + +' ---------------------------------------------------------------- +' Set a RAM bank in addresses $c000-$ffff, update BANKM, +' and return with INTerrupts preserved (unchanged) +' Only works on 128K and compatible models. +' Parameters: +' Ubyte: bank number 0,1,2,3,4,5,6,7 +' Changes: +' A, B, C +' Preserves: +' D, E, H, L are not used +' +' Version zx81sd: "banco" aqui es puramente logico. Con 7 se asegura que +' la pagina de MSFS este mapeada en el bloque 7; con cualquier otro +' valor SOLO se anota el numero (la pagina de MSFS se queda RESIDENTE, +' no se desmapea). Motivo: SaveBackgroundAndDrawSpriteRegisteredInMSFS +' (el que dibuja en el bucle principal, y el que crea imagenes +' desplazadas bajo demanda) accede a la MSFS SIN envolver con +' Get/SetBank -- en el diseno original no lo necesita porque en 128K el +' banco 7 se queda mapeado en $c000 (SetDrawingScreen7) y en 48K la +' MSFS esta en RAM plana siempre visible. Una primera version de este +' override "liberaba" el bloque 7 a la pagina 63 al restaurar tras +' Init/Register... y el dibujo del bucle principal leia mascaras y +' graficos de la pagina 63 (basura): sprites como lineas verticales. +' Si un programa usa el bloque 7 para su propio banking de datos, +' debe remapear su pagina el mismo y volver a llamar aqui con 7 antes +' de usar MSFS (documentado tambien en la cabecera del fichero). +' +' No hace falta preservar interrupciones: en zx81sd estan +' permanentemente deshabilitadas (DI todo el tiempo). IMPORTANTE: +' escrita en ASM a mano (no en BASIC plano) para respetar EXACTAMENTE +' el contrato de registros de arriba -- RegisterSpriteImageInMSFS y +' companeros confian en que D,E,H,L sobrevivan a esta llamada (por +' ejemplo, para no perder spriteImageAddr, que llega en HL); una +' implementacion en BASIC normal no da esa garantia (usa registros +' libremente por dentro) y provocaba que todos los sprites se +' registraran en la misma direccion. +' ---------------------------------------------------------------- +SUB FASTCALL SetBankPreservingINTs(bankNumber AS UByte) +ASM + PROC + LOCAL NO_MAP + ld (_ZX81SD_MSFS_Bank), a + cp 7 + jr nz, NO_MAP ; banco logico <> 7: solo anotar, la + ; pagina de MSFS se queda residente + push de + push hl + ld d, MaskedSprites_MSFS_Page + ld e, 0 + push de ; parametro 'page' de Map (byte alto = D) + ld a, 7 ; parametro 'block' de Map (bloque 7, fijo) + call _Map + pop hl + pop de +NO_MAP: + ENDP +END ASM +END SUB + + +' ---------------------------------------------------------------- +' Get which RAM bank is set in addresses $c000-$ffff +' Only works on 128K and compatible models. +' Preserves: +' B, C, D, E, H, L are not used +' Returns: +' UByte: bank number 0,1,2,3,4,5,6,7 +' +' Version zx81sd: devuelve el "banco logico" guardado por la ultima +' llamada a SetBankPreservingINTs (ver arriba). En ASM a mano por el +' mismo motivo que SetBankPreservingINTs: preservar B,C,D,E,H,L de +' verdad para las rutinas de MSFS que dependen de ello. +' ---------------------------------------------------------------- +FUNCTION FASTCALL GetBankPreservingRegs() AS UByte +ASM + ld a, (_ZX81SD_MSFS_Bank) +END ASM +END FUNCTION + + +' ---------------------------------------------------------------- +' Check whether memory paging works (128,+2,...) or not (16,48) +' Returns: +' UByte: 1 if paging works, 0 if it does not +' +' Version zx81sd: siempre devuelve 0. No existe paginacion de pantalla +' visible al estilo Spectrum 128K (un unico framebuffer fisico, bloque +' 6); ver la nota de cabecera de este fichero sobre por que esto no +' afecta a que MSFS funcione (usa el mapeador propio, no esta funcion). +' ---------------------------------------------------------------- +FUNCTION FASTCALL CheckMemoryPaging() AS UByte + RETURN 0 +END FUNCTION + + +' ---------------------------------------------------------------- +' Set the visible screen (either in bank5 or bank7) +' and updates the system variable BANKM. +' Only works on 128K and compatible models. +' Parameters: +' Ubyte: bank number 5 or 7 +' Preserves: +' D, E, H, L are not used +' +' Version zx81sd: no existe hardware de doble pantalla visible +' intercambiable (bancos 5/7 del Spectrum 128K) -- zx81sd solo tiene un +' framebuffer fisico (bloque 6, SCREEN_ADDR fijo en $C000). Se deja como +' stub seguro (no toca $5B5C ni el puerto $7FFD, que no existen aqui); +' CheckMemoryPaging() devuelve 0, asi que en la practica nunca se llama +' de verdad. Doble buffer de pantalla real (alternando que pagina +' respalda el bloque 6 via el mapeador) seria una funcionalidad aparte, +' no cubierta aqui. +' ---------------------------------------------------------------- +SUB FASTCALL SetVisibleScreen(bankNumber AS UByte) +END SUB + + +' ---------------------------------------------------------------- +' Returns the bank of visible screen (either 5 or 7) +' according to system variable BANKM. +' Only works on 128K and compatible models. +' Returns: +' UByte: bank 5 or 7 +' +' Version zx81sd: stub seguro, ver SetVisibleScreen arriba. +' ---------------------------------------------------------------- +FUNCTION FASTCALL GetVisibleScreen() AS UByte + RETURN 5 +END FUNCTION + + +' ---------------------------------------------------------------- +' Toggles the visible screen (from 5 to 7, or from 7 to 5) +' and updates the system variable BANKM. +' Only works on 128K and compatible models. +' +' Version zx81sd: stub seguro, ver SetVisibleScreen arriba. +' ---------------------------------------------------------------- +SUB FASTCALL ToggleVisibleScreen() +END SUB + + +' ---------------------------------------------------------------- +' Copy contents of screen5 to screen7 (display file + attribs) +' Only works on 128K and compatible models. +' +' Version zx81sd: stub seguro, ver SetVisibleScreen arriba. +' ---------------------------------------------------------------- +SUB FASTCALL CopyScreen5ToScreen7() +END SUB + + +' ---------------------------------------------------------------- +' Copy contents of screen7 to screen5 (display file + attribs) +' Only works on 128K and compatible models. +' +' Version zx81sd: stub seguro, ver SetVisibleScreen arriba. +' ---------------------------------------------------------------- +SUB FASTCALL CopyScreen7ToScreen5() +END SUB + + +' ---------------------------------------------------------------- +' Set ScreenBufferAddr and AttrBufferAddr to screen5 +' Only works on 128K and compatible models. +' +' Version zx81sd: stub seguro, ver SetVisibleScreen arriba. IMPORTANTE: +' NO se puede simplemente copiar el original -- SetScreenBufferAddr($4000) +' redirigiria SCREEN_ADDR a plena zona de codigo/runtime ($1000-$7FFF en +' nuestro mapa de memoria), corrompiendo cualquier PRINT/grafico +' posterior. Se deja vacia. +' ---------------------------------------------------------------- +SUB FASTCALL SetDrawingScreen5() +END SUB + + +' ---------------------------------------------------------------- +' Put screen7 at $c000 (in case it is not), and +' Set ScreenBufferAddr and AttrBufferAddr to screen7 +' Only works on 128K and compatible models. +' Returns: +' Bank7 is set at $c000, old bank is removed +' UByte: bank that was at $c000 (to restore it manually IYW) +' +' Version zx81sd: stub seguro, ver SetVisibleScreen arriba. $c000/$d800 +' coinciden por casualidad con nuestro SCREEN_ADDR/SCREEN_ATTR_ADDR +' reales, pero esta funcion tambien mapea banco7 (aqui reinterpretado +' como el banco de MSFS) al bloque 7, no al bloque 6 (pantalla) -- no +' hay overlap real posible entre "pantalla" y "banco 7" en zx81sd, asi +' que se deja como no-op en vez de fingir un intercambio que no existe. +' ---------------------------------------------------------------- +FUNCTION FASTCALL SetDrawingScreen7() AS UByte + RETURN 5 +END FUNCTION + + +' ---------------------------------------------------------------- +' Toggle ScreenBufferAddr and AttrBufferAddr between screen5,7 +' Only works on 128K and compatible models. +' +' Version zx81sd: stub seguro, ver SetVisibleScreen arriba. +' ---------------------------------------------------------------- +SUB FASTCALL ToggleDrawingScreen() +END SUB + + +' ---------------------------------------------------------------- +' MaskedSpritesBackgroundSet = 0 or 1 is the Set of Backgrounds +' +' MaskedSpritesBackground(i) is the address where Background i begins +' +' NumberofMaskedSprites is a MACRO that should be #define-d +' before #include-ing this file +' +' MaskedSprites_USE_STACK_TRANSFER is a MACRO that should be #define-d +' if you want this library to use Stack PUSH+POP instructions to speed-up +' transfer of information between different parts of the RAM +' (this library will disable interrupts before using Stack Transfer) +' +' ChangeMaskedSpritesBackgroundSet() changes the Set of Backgrounds +' Returns: +' Byte: new value of MaskedSpritesBackgroundSet (IYW to use it) +' ---------------------------------------------------------------- +dim MaskedSpritesBackgroundSet AS UByte = 0 + +#define MaskedSpritesBackground(i) ( $db00+48*CAST(UInteger,i)+48*CAST(UInteger,NumberofMaskedSprites)*MaskedSpritesBackgroundSet ) + +FUNCTION FASTCALL ChangeMaskedSpritesBackgroundSet() AS UByte + MaskedSpritesBackgroundSet = MaskedSpritesBackgroundSet bXOR 1 + RETURN MaskedSpritesBackgroundSet +END FUNCTION + + +' ---------------------------------------------------------------- +' MaskedSprites_NEXT_ROW is a MACRO of ASM code, based on code from +' https://zonadepruebas.com/viewtopic.php?f=15&t=8372&start=40#p81507 +' and found by Joaquin Ferrero +' ---------------------------------------------------------------- +#define MaskedSprites_NEXT_ROW \ + ld a,e ; 4 A = E \ + sub 224 ; 7 A = E + 32 (SUB 224 is similar to +32) \ + ; CF = 0/1 iff E >=/< 224 iff a third is/isn't crossed \ + ld e,a ; 4 \ + sbc a,a ; 4 A = 0/255 \ + and 248 ; 7 A = 0/248 (248 = -8) \ + add a,d ; 4 A = D/D-8 iff a third is/isn't crossed \ + ld d,a ; 4 += 34 Ts + + +' ---------------------------------------------------------------- +' Save background and Draw sprite in screen +' Parameters: +' UByte: X coordinate (0:left to 240:right) +' UByte: Y coordinate (0:up to 176:down) +' UInteger: Address where background will be saved +' UInteger: Address where sprite image begins +' ---------------------------------------------------------------- +SUB FASTCALL SaveBackgroundAndDrawSprite(X AS UByte, Y AS UByte, backgroundAddr AS UInteger, spriteImageAddr AS UInteger) +ASM + PROC + LOCAL shiftright, shiftleft, noshift + LOCAL loopSR, loopSL, loopNS, loopR, loopL, branchSR, branchSL, branchNS + ; A = X + pop de ; returnAddr + exx + pop bc ; B = Y + ld c,a ; C = X +; BEGIN code from https://skoolkid.github.io/rom/asm/22AA.html + rlca + rlca + rlca ; A = %c4c3c2c1c0c7c6c5 + xor b + and %11000111 + xor b ; A = %c4c3b5b4b3c7c6c5 + rlca + rlca + ld e,a ; E = %b5b4b3c7c6c5c4c3 + ld a,b + and %11111000 + rra + rra + rra ; A = %.0.0.0b7b6b5b4b3 + xor b + and %11111000 + xor b + ld d,a ; D = %.0.0.0b7b6b2b1b0 +; END code from https://skoolkid.github.io/rom/asm/22AA.html + ld hl,(.core.SCREEN_ADDR) + add hl,de + ex de,hl ; DE = screenAddr where drawing will start + ld a,c; ; A = X + and 7 + jr z,noshift ; jump if X is a multiple of 8 (unlikely) + ; continue if sprite must be shifted + cp 4 ; is >= 4 ? + jp nc,shiftleft ; shift left if X MOD 8 = 4,5,6,7 + ; shift right if X MOD 8 = 1,2,3 +shiftright: + pop bc ; backgroundAddr + exx + pop hl ; spriteImageAddr + push de ; returnAddr + push ix + ld ixh,16 ; 16 scanlines + ld ixl,a ; IXl = X MOD 8 = 1,2,3,4 +loopSR: + ld a,(hl) ; mask1 + inc hl + ld c,(hl) ; graph1 + inc hl + ld d,(hl) ; mask2 + inc hl + ld e,(hl) ; graph2 + inc hl + push hl ; spriteImageAddr + + ld hl,$FF00 ; H = 255 , L = 0 + ld b,ixl +loopR: + scf ; 4 + rra ; 4; SCF + RRA injects a 1 in bit7 of A + rr d ; 8 + rr h ; 8 + srl c ; 8; ShiftRightLogical injects a 0 in bit7 of C + rr e ; 8 + rr l ; 8 + djnz loopR ; 4+4+8+8+8+8+8 = 48 Ts + ld b,a + push hl ; H,L = mask,graph 3rd byte + push de ; D,E = mask,graph 2nd byte + push bc ; B,C = mask,graph 1st byte + exx + + ld a,(de) ; screen + ld (bc),a ; save + inc bc + pop hl + and h ; mask + or l ; graph + ld (de),a ; 1st byte done + inc e + + ld a,(de) ; screen + ld (bc),a ; save + inc bc + pop hl + and h ; mask + or l ; graph + ld (de),a ; 2nd byte done + inc e + + ld a,(de) ; screen + ld (bc),a ; save + inc bc + pop hl + and h ; mask + or l ; graph + ld (de),a ; 3rd byte done + dec e + dec e + + inc d + ld a,d + and 7 + jr z,branchSR ; 7Ts no jump (7/8 times), 12Ts jump (1/8 times) + exx + pop hl ; spriteImageAddr + dec ixh + jp nz,loopSR + pop ix + ret +branchSR: + MaskedSprites_NEXT_ROW + exx + pop hl ; spriteImageAddr + dec ixh + jp nz,loopSR + pop ix + ret + +noshift: + pop bc ; backgroundAddr + pop hl ; spriteImageAddr + exx + push de ; returnAddr + exx + push ix + ld ixh,16 ; 16 scanlines +loopNS: + ld a,(de) ; screen + ld (bc),a; ; save + inc bc + and (hl); ; mask + inc hl + or (hl) ; graph + inc hl + ld (de),a ; 1st byte done + inc e + + ld a,(de) ; screen + ld (bc),a ; save + inc bc + and (hl); ; mask + inc hl + or (hl) ; graph + inc hl + ld (de),a ; 2nd byte done + dec e + + inc d + ld a,d + and 7 + jr z,branchNS ; 7Ts no jump (7/8 times), 12Ts jump (1/8 times) + dec ixh + jp nz,loopNS + pop ix + ret +branchNS: + MaskedSprites_NEXT_ROW + dec ixh + jp nz,loopNS + pop ix + ret + +shiftleft: + pop bc ; backgroundAddr + exx + pop hl ; spriteImageAddr + push de ; returnAddr + push ix + ld ixh,16 ; 16 scanlines + sub 8 + neg ; A = 8 - oldA + ld ixl,a ; IXl = 8 - (X MOD 8) = 8 - 4,5,6,7 = 4,3,2,1 +loopSL: + ld a,(hl) ; mask1 + inc hl + ld c,(hl) ; graph1 + inc hl + ld d,(hl) ; mask2 + inc hl + ld e,(hl) ; graph2 + inc hl + push hl ; spriteImageAddr + + ld hl,$FF00 ; H = 255 , L = 0 + ld b,ixl +loopL: + sll d ; 8; ShiftLeftLogical injects a 1 in bit0 of D + rla ; 4 + rl h ; 8 + sla e ; 8; ShiftLeftArithmetic injects a 0 in bit0 of E + rl c ; 8 + rl l ; 8 + djnz loopL ; 8+4+8+8+8+8 = 44 Ts + ld b,a + push de ; D,E = mask,graph 3rd byte + push bc ; B,D = mask,graph 2nd byte + push hl ; H,L = mask,graph 1st byte + exx + + ld a,(de) ; screen + ld (bc),a ; save + inc bc + pop hl + and h ; mask + or l ; graph + ld (de),a ; 1st byte done + inc e + + ld a,(de) ; screen + ld (bc),a ; save + inc bc + pop hl + and h ; mask + or l ; graph + ld (de),a ; 2nd byte done + inc e + + ld a,(de) ; screen + ld (bc),a ; save + inc bc + pop hl + and h ; mask + or l ; graph + ld (de),a ; 3rd byte done + dec e + dec e + + inc d + ld a,d + and 7 + jr z,branchSL ; 7Ts no jump (7/8 times), 12Ts jump (1/8 times) + exx + pop hl ; spriteImageAddr + dec ixh + jp nz,loopSL + pop ix + ret +branchSL: + MaskedSprites_NEXT_ROW + exx + pop hl ; spriteImageAddr + dec ixh + jp nz,loopSL + pop ix + ret + ENDP +END ASM +END SUB + + +' ---------------------------------------------------------------- +' Restore background in screen +' Parameters: +' UByte: X coordinate (0:left to 240:right) +' UByte: Y coordinate (0:up to 176:down) +' UInteger: Address where saved background begins +' ---------------------------------------------------------------- +SUB FASTCALL RestoreBackground(X AS UByte, Y AS UByte, backgroundAddr AS UInteger) +ASM + PROC + LOCAL loop2b, loop3b, branch2b, branch3b + ; A = X + pop de ; returnAddr + exx + pop bc ; B = Y + ld c,a ; C = X +; BEGIN code from https://skoolkid.github.io/rom/asm/22AA.html + rlca + rlca + rlca ; A = %c4c3c2c1c0c7c6c5 + xor b + and %11000111 + xor b ; A = %c4c3b5b4b3c7c6c5 + rlca + rlca + ld e,a ; E = %b5b4b3c7c6c5c4c3 + ld a,b + and %11111000 + rra + rra + rra ; A = %.0.0.0b7b6b5b4b3 + xor b + and %11111000 + xor b + ld d,a ; D = %.0.0.0b7b6b2b1b0 +; END code from https://skoolkid.github.io/rom/asm/22AA.html + ld hl,(.core.SCREEN_ADDR) + add hl,de + ex de,hl ; DE = screenAddr where restoring will start + pop hl ; backgroundAddr + exx + push de ; returnAddr + exx + ld a,c; ; A = X + ld bc,$10FF ; B = 16, C = 255 (up to 255 LDIs do not change B) + and 7 + jr z,loop2b ; jump if X is a multiple of 8 (unlikely) + ; continue if restoring 3 bytes per scanline +; 3bytes per scanline +loop3b: + ldi ; 16 Ts vs 7+7+6+4=24 Ts + ldi + ldi ; 3 bytes background restored to screen + dec de ; last LDI could have increased D if initially E=253... + dec e ; ...so DEC DE restores D in that case + dec e + + inc d + ld a,d + and 7 + jr z,branch3b ; 7Ts no jump (7/8 times), 12Ts jump (1/8 times) + djnz loop3b + ret +branch3b: + MaskedSprites_NEXT_ROW + djnz loop3b + ret +; 2bytes per scanline +loop2b: + ldi ; 16 Ts vs 7+7+6+4=24 Ts + ldi ; 2 bytes background restored to screen + dec de ; last LDI could have increased D if initially E=254... + dec e ; ...so DEC DE restores D in that case + + inc d + ld a,d + and 7 + jr z,branch2b ; 7Ts no jump (7/8 times), 12Ts jump (1/8 times) + djnz loop2b + ret +branch2b: + MaskedSprites_NEXT_ROW + djnz loop2b + ret + ENDP +END ASM +END SUB + + +' ---------------------------------------------------------------- +' Structure of the Masked Sprites FileSystem (MSFS): +' +' MSFS consists of many blocks of 96 bytes +' MSFS starts at address stored in MaskedSpritesFileSystemStart, e.g., 56736 +' MSFS length is a multiple of 96 bytes, e.g., 8736 = 91*96 bytes +' With that length, MSFS ranges from 56736 to 65471 +' MSFS stores Images (mask+graph) of Masked Sprites +' Blocks used are marked in the FSB (https://en.wikipedia.org/wiki/Free-space_bitmap) +' +' First block of the MSFS (superblock, block number = 0) +' start+0 DEFB number of blocks in MSFS = bits of the FSB, e.g., 91 +' start+1 DEFB number of bytes of the FSB, e.g., 12 (91/8 = 11.4) +' start+2-13 DEFS 12 is the FSB (12 bytes = 96 bits is enough for 91 blocks) +' start+14-15 unused +' +' Block of an unshifted image (block number n = 0,...,90) +' start+n*96 \ +' ... | 16 bytes unused for n>0, superblock for n=0 +' start+n*96+15 / +' start+n*96+16-17 DEFW start+n*96+32 = start of unshifted image +' start+n*96+18-19 DEFW address of block for image shifted 1 pixel, or 0 if not used +' start+n*96+20-21 DEFW address of block for image shifted 2 pixels, or 0 if not used +' ... +' start+n*96+30-31 DEFW address of block for image shifted 7 pixels, or 0 if not used +' start+n*96+32-95 DEFS 64 the unshifted image (mask+graph) +' +' Block number 0 (n=0) is special because it contains +' 16 bytes for the superblock (including 2 unused bytes) + +' 16 bytes for addressing unshifted and shifted versions of the first image + +' 64 bytes for the first unshifted image +' +' Block of a shifted image (block number m = 1,...,90, note m>0) +' start+m*96+0 DEFS 96 the shifted image (mask+graph) +' ---------------------------------------------------------------- +dim MaskedSpritesFileSystemStart AS UInteger = 0 +' ---------------------------------------------------------------- +' MaskedSpritesFileSystemStart = address where Masked Sprites FileSystem starts +' +' InitMaskedSpritesFileSystem() inits the MSFS +' Returns: +' UInteger: value of MaskedSpritesFileSystemStart (IYW to use it) +' +' Version zx81sd: MaskedSpritesFileSystemStart es una direccion FIJA +' dentro del bloque 7 ($E000, la pagina dedicada MaskedSprites_MSFS_Page) +' en vez de "lo que quede hasta $FFFF" (el original asumia RAM plana +' Spectrum ahi, que en zx81sd es una ventana paginada de 8K, no RAM +' contigua). Con $E000: l = Int((65536-57344)/96) = 85 bloques -- toda +' la MSFS cabe de sobra en los 8K del bloque 7. El resto de la funcion +' (calculo de bytes del FSB, pokes) es identico al original. Se llama +' siempre a SetBankPreservingINTs(7) (no gated por CheckMemoryPaging(), +' que aqui es irrelevante para MSFS -- ver cabecera del fichero). +' ---------------------------------------------------------------- +FUNCTION FASTCALL InitMaskedSpritesFileSystem() AS UInteger + DIM b AS UByte + DIM l, j AS UInteger + + b = GetBankPreservingRegs() + if b<>7 then SetBankPreservingINTs(7) + ' Llamada BASIC explicita a Map(), redundante con la que ya hace + ' SetBankPreservingINTs por ASM a mano (mapea el mismo bloque/pagina + ' otra vez, sin efecto): el analisis de "codigo muerto" del + ' compilador no cuenta las llamadas hechas desde ASM como uso, y sin + ' esto Map() se elimina del binario -> "Undefined GLOBAL label" al + ' intentar llamarla desde dentro de SetBankPreservingINTs. + Map(7, MaskedSprites_MSFS_Page) + MaskedSpritesFileSystemStart = $E000 + l = -MaskedSpritesFileSystemStart + l = Int(l/96) + poke MaskedSpritesFileSystemStart, l ' MSFS blocks = FSB bits + if l=0 then STOP + l = 1+Int((l-1)/8) + poke MaskedSpritesFileSystemStart+1,l ' FSB bytes + ' Limpiar el FSB (bitmap de bloques libres, bytes start+2..start+1+l). + ' Ni esta version ni la original de zx48k lo inicializaban: en el + ' Spectrum no hace falta porque el test de RAM de la ROM deja toda + ' la memoria a cero en el arranque, asi que el bitmap nace "todo + ' libre" gratis. En zx81sd la pagina del bloque 7 llega con basura + ' de fabrica: todos los bits aparecian a 1 ("ocupado"), + ' FindFirstUnusedBlockInMSFS recorria el FSB entero sin encontrar + ' hueco y RegisterSpriteImageInMSFS devolvia siempre 0 (lleno). + FOR j = MaskedSpritesFileSystemStart+2 TO MaskedSpritesFileSystemStart+1+l + poke j, 0 + NEXT j + if b<>7 then SetBankPreservingINTs(b) + return MaskedSpritesFileSystemStart + +' Now, some assembly routines needed for next SUB/FUNCTIONs +ASM +; ---------------------------------------------------------------- +; Find memory addres in MSFS for the start of a block +; Parameters: +; L = blocknumber = n = 0,1,2,... (probably less than 200) +; Preserves: +; A, B, C are not used +; Returns: +; HL = start+n*96 +; DE = start +; ---------------------------------------------------------------- +FindMemoryAdressForBlockInMSFS: + PROC + ld h,0 + ld d,h + ld e,l ; HL = DE = n = blocknumber + add hl,de + add hl,de ; HL = DE*3 + add hl,hl + add hl,hl + add hl,hl + add hl,hl + add hl,hl ; HL = DE*3*(2^5) = DE*96 + ld de,(_MaskedSpritesFileSystemStart) + add hl,de; ; HL = start+n*96 + ret + ENDP + +; ---------------------------------------------------------------- +; Find First Unused Block in MSFS and (optionally) Book it +; Parameters: +; CarryFlag = 0/1 don't/do Book it +; Preserves: +; C is not used +; Returns: +; CarryFlag = 0 if found, 1 if not found +; A = FirstUnusedBlock if found +; HL = start+A*96 if found +; ---------------------------------------------------------------- +FindFirstUnusedBlockInMSFS: + PROC + LOCAL loop1, loop2, full, found, loop3, compute + ex af,af' ; saves CarryFlag + ld hl,(_MaskedSpritesFileSystemStart) + ld d,(hl) + ld e,d ; D = E = number of bits in the FSB = N + inc hl + inc hl ; HL points to the first byte in the FSB +loop1: + ld a,(hl) + ld b,8 +loop2: + rrca + jr nc,found + dec e + jr z,full + djnz loop2 + inc hl + jp loop1 +full: ; E = 0 + scf ; CarryFlag=1 = ERROR + ret +found: ; E = N,N-1,N-2,...,1 + ex af,af' + jr nc,compute + ex af,af' + rlca ; undo last RRCA + or 1 ; mark this block as used +loop3: + rrca ; finish the 8-bit rotation to... + djnz loop3 ; leave bits where they were + ld (hl),a ; effective booking +compute: + ld a,d ; A = N + sub e ; A = 0,1,2,...,N-1 = blocknumber + ld l,a + call FindMemoryAdressForBlockInMSFS + and a ; CarryFlag=0 = OK + ret ; A = blocknumber, HL = start+A*96 + ENDP +END ASM + +END FUNCTION + + +' ---------------------------------------------------------------- +' Get Number of Free Blocks in MSFS +' Returns: +' UByte: number of free blocks in MSFS +' ---------------------------------------------------------------- +FUNCTION FASTCALL GetNumberofFreeBlocksInMSFS() AS UByte +ASM + PROC + LOCAL loop1, loop2, exit + call _GetBankPreservingRegs + cp 7 ; ZeroFlag=0 (NZ) iff _GetBankPreservingRegs returns A<>7 + push af ; ZF and original RAM bank when FUNCTION was called + ld a,7 + call nz,_SetBankPreservingINTs ; set RAM7 if _GetBankPreservingRegs returns A<>7 + xor a ; A = 0 = number of reset bits in the FSB + ld hl,(_MaskedSpritesFileSystemStart) + ld d,(hl) ; D = number of bits in the FSB = N + ld e,a ; E = 0 always + inc hl + inc hl ; HL points to the first byte in the FSB +loop1: + ld c,(hl) + ld b,8 +loop2: + rr c + ccf ; CarryFlag = 0/1 = bit in the FSB is set/reset + adc a,e ; A += CarryFlag = number of reset bits in the FSB + dec d + jr z,exit ; return A if all bits in FSB have been checked + djnz loop2 + inc hl + jr loop1 +exit: + ex af,af' ; A' = number of free blocks in MSFS + pop af ; ZF and original RAM bank when FUNCTION was called + call nz,_SetBankPreservingINTs + ex af,af' ; A = number of free blocks in MSFS + ret + ENDP +END ASM +END FUNCTION + + +' ---------------------------------------------------------------- +' Register spriteImage in MSFS +' Parameters: +' UInteger: address where spriteImage begins +' Returns: +' UInteger: registry number in the MSFS = start+n*96+16 if OK +' 0 if not OK +' ---------------------------------------------------------------- +FUNCTION FASTCALL RegisterSpriteImageInMSFS(spriteImageAddr AS UInteger) AS UInteger +ASM + PROC + LOCAL full, exit + call _GetBankPreservingRegs + cp 7 ; ZeroFlag=0 (NZ) iff _GetBankPreservingRegs returns A<>7 + push af ; ZF and original RAM bank when FUNCTION was called + push hl ; spriteImageAddr + ld a,7 + call nz,_SetBankPreservingINTs ; set RAM7 if it was not set + scf + call FindFirstUnusedBlockInMSFS ; and book it (SCF) + jr c,full + ld bc,16 + add hl,bc + push hl ; HL = start+A*96+16 + ld d,h + ld e,l + inc de ; DE = start+A*96+17 + dec bc ; BC = 15 + ld (hl),0 + ldir ; reset RAM from start+A*96+16 to start+A*96+31 (incl.) + pop hl ; HL = start+A*96+16 + ld b,h + ld c,l ; BC = start+A*96+16 + ld (hl),e ; DE = start+A*96+32 (after last LDIR) + inc hl + ld (hl),d ; start+A*96+16-17 DEFW start+A*96+32 + pop hl ; spriteImageAddr + push bc ; BC = start+A*96+16 + ld bc,64 + ldir ; transfer from spriteImageAddr to start+A*96+32 + pop hl ; HL = start+A*96+16 +exit: + pop af ; ZF and original RAM bank when FUNCTION was called + call nz,_SetBankPreservingINTs + ret +full: + pop hl ; spriteImageAddr + ld hl,0 + jr exit + ENDP +END ASM +END FUNCTION + + +' ---------------------------------------------------------------- +' Register spriteGraph and spriteMask in MSFS +' (useful when different Graphs share the same Mask) +' +' Data in spriteGraph and spriteMask MUST be in "putchars format" +' +' Parameters: +' UInteger: address where spriteGraph begins +' UInteger: address where spriteMask begins +' Returns: +' UInteger: registry number in the MSFS = start+n*96+16 if OK +' 0 if not OK +' ---------------------------------------------------------------- +FUNCTION FASTCALL RegisterSpriteGraphAndMaskInMSFS(spriteGraphAddr AS UInteger,spriteMaskAddr AS UInteger) AS UInteger +ASM + PROC + LOCAL full, exit, loop1, loop2 + pop de ; returnAddr + pop bc ; spriteMaskAddr + push de ; returnAddr +; stack is empty. Now we will push data to be preserved + exx ; HL' = spriteGraphAddr, BC' = spriteMaskAddr + call _GetBankPreservingRegs + cp 7 ; ZeroFlag=0 (NZ) iff _GetBankPreservingRegs returns A<>7 + push af ; ZF and original RAM bank when FUNCTION was called + ld a,7 + call nz,_SetBankPreservingINTs ; set RAM7 if it was not set + scf + call FindFirstUnusedBlockInMSFS ; and book it (SCF) + jr c,full + ld bc,16 + add hl,bc + push hl ; HL = start+A*96+16 + ld d,h + ld e,l + inc de ; DE = start+A*96+17 + dec bc ; BC = 15 + ld (hl),0 + ldir ; reset RAM from start+A*96+16 to start+A*96+31 (incl.) + pop hl ; HL = start+A*96+16 + ld (hl),e ; DE = start+A*96+32 (after last LDIR) + inc hl + ld (hl),d ; start+A*96+16-17 DEFW start+A*96+32 + dec hl + push hl ; return value HL = start+A*96+16 + push de ; DE = start+A*96+32 DEFS 64 the unshifted image (mask+graph) + exx ; HL = spriteGraphAddr, BC = spriteMaskAddr + pop de ; DE = DEFS 64 the unshifted image (mask+graph) + push ix + push de ; DE = start+A*96+32 + ld ixl,16 +loop1: + ld a,(bc) ; mask + ld (de),a + inc bc + inc de + ld a,(hl) ; graph + ld (de),a + inc hl + inc de + inc de + inc de + dec ixl + jp nz,loop1 + pop de ; DE = start+A*96+32 + inc de + inc de ; DE = start+A*96+32 +2 + ld ixl,16 +loop2: + ld a,(bc) ; mask + ld (de),a + inc bc + inc de + ld a,(hl) ; graph + ld (de),a + inc hl + inc de + inc de + inc de + dec ixl + jp nz,loop2 + pop ix + pop hl ; return value HL = start+A*96+16 +exit: + pop af ; ZF and original RAM bank when FUNCTION was called + call nz,_SetBankPreservingINTs + ret +full: + ld hl,0 + jr exit + ENDP +END ASM +END FUNCTION + + +' ---------------------------------------------------------------- +' Save background and Draw sprite registered in the MSFS +' Parameters: +' UByte: X coordinate (0:left to 240:right) +' UByte: Y coordinate (0:up to 176:down) +' UInteger: address where background will be saved +' UInteger: registry number in the MSFS for the spriteImage +' ---------------------------------------------------------------- +SUB FASTCALL SaveBackgroundAndDrawSpriteRegisteredInMSFS(X AS UByte, Y AS UByte, backgroundAddr AS UInteger, spriteImageReg AS UInteger) +ASM + PROC + LOCAL full, makeShiftedImage, loopMSI, loopMSI1 + LOCAL useShiftedImage, loopUSI, branchUSI, exitUSI + LOCAL noshift, loopNS, branchNS, exitNS + ; A = X + pop de ; returnAddr + exx + pop bc ; B = Y + ld c,a ; C = X +; BEGIN code from https://skoolkid.github.io/rom/asm/22AA.html + rlca + rlca + rlca ; A = %c4c3c2c1c0c7c6c5 + xor b + and %11000111 + xor b ; A = %c4c3b5b4b3c7c6c5 + rlca + rlca + ld e,a ; E = %b5b4b3c7c6c5c4c3 + ld a,b + and %11111000 + rra + rra + rra ; A = %.0.0.0b7b6b5b4b3 + xor b + and %11111000 + xor b + ld d,a ; D = %.0.0.0b7b6b2b1b0 +; END code from https://skoolkid.github.io/rom/asm/22AA.html + ld hl,(.core.SCREEN_ADDR) + add hl,de + ex de,hl ; DE = screenAddr where drawing will start + ld a,c; ; A = X, keep it secret, keep it safe + pop bc ; BC = backgroundAddr + pop hl ; HL = spriteImageReg + exx + push de ; returnAddr + exx +; stack is empty. Now we will push data to be preserved + push ix + ld ixh,16 ; 16 scanlines + and 7 + jr z,noshift ; jump if X is a multiple of 8 (unlikely) + push de ; DE = screenAddr where drawing will start + push bc ; BC = backgroundAddr + ld b,0 + ld c,a ; BC = A = 1,2,3,4,5,6,7 + add hl,bc + add hl,bc ; HL = start+n*96+16+C*2 DEFW address of block for image shifted C pixels, or 0 if not used + ld e,(hl) + inc hl + ld d,(hl) ; DE = address for image shifted C pixels, or 0 if not used + ld a,d + or e + jp z,makeShiftedImage +;UseShiftedImage (USI) +useShiftedImage: + ex de,hl ; HL = address for image shifted C pixels + pop bc ; BC = backgroundAddr + pop de ; DE = screenAddr where drawing will start +#ifdef MaskedSprites_USE_STACK_TRANSFER + ld a,i ; IFF2=0/1=DI/EI is saved in PF=0/1=Odd/Even + jp pe,1f ; if PF=Even=1, it is sure that IFF2=1=EI + ld a,i ; read IFF2 again to ensure that IFF2=0=DI +1: ex af,af' + di + ld (exitUSI+1),sp + ld sp,hl +#endif +loopUSI: + ld a,(de) ; screen + ld (bc),a; ; save + inc bc +#ifdef MaskedSprites_USE_STACK_TRANSFER + pop hl + and l ; mask + or h ; graph +#else + and (hl); ; mask + inc l + or (hl) ; graph + inc hl +#endif + ld (de),a ; 1st byte done + inc e + + ld a,(de) ; screen + ld (bc),a; ; save + inc bc +#ifdef MaskedSprites_USE_STACK_TRANSFER + pop hl + and l ; mask + or h ; graph +#else + and (hl); ; mask + inc l + or (hl) ; graph + inc hl +#endif + ld (de),a ; 2nd byte done + inc e + + ld a,(de) ; screen + ld (bc),a; ; save + inc bc +#ifdef MaskedSprites_USE_STACK_TRANSFER + pop hl + and l ; mask + or h ; graph +#else + and (hl); ; mask + inc l + or (hl) ; graph + inc hl +#endif + ld (de),a ; 3rd byte done + dec e + dec e + + inc d + ld a,d + and 7 + jr z,branchUSI ; 7Ts no jump (7/8 times), 12Ts jump (1/8 times) + dec ixh + jp nz,loopUSI +exitUSI: +#ifdef MaskedSprites_USE_STACK_TRANSFER + ld sp,$1234 + pop ix + ex af,af' + ret po ; Return with DI if IFF2=0=DI at the beginning + ei ; Return with EI if IFF2=1=EI at the beginning + ret +#else + pop ix + ret +#endif +branchUSI: + MaskedSprites_NEXT_ROW + dec ixh + jp nz,loopUSI +#ifdef MaskedSprites_USE_STACK_TRANSFER + jp exitUSI +#else + pop ix + ret +#endif +;NoShift (NS) +noshift: + ld a,(hl) ; HL = spriteImageReg + inc hl + ld h,(hl) + ld l,a ; HL = start of unshifted image +#ifdef MaskedSprites_USE_STACK_TRANSFER + ld a,i ; IFF2=0/1=DI/EI is saved in PF=0/1=Odd/Even + jp pe,1f ; if PF=Even=1, it is sure that IFF2=1=EI + ld a,i ; read IFF2 again to ensure that IFF2=0=DI +1: ex af,af' + di + ld (exitNS+1),sp + ld sp,hl +#endif +loopNS: + ld a,(de) ; screen + ld (bc),a; ; save + inc bc +#ifdef MaskedSprites_USE_STACK_TRANSFER + pop hl + and l ; mask + or h ; graph +#else + and (hl); ; mask + inc l + or (hl) ; graph + inc hl +#endif + ld (de),a ; 1st byte done + inc e + + ld a,(de) ; screen + ld (bc),a ; save + inc bc +#ifdef MaskedSprites_USE_STACK_TRANSFER + pop hl + and l ; mask + or h ; graph +#else + and (hl); ; mask + inc l + or (hl) ; graph + inc hl +#endif + ld (de),a ; 2nd byte done + dec e + + inc d + ld a,d + and 7 + jr z,branchNS ; 7Ts no jump (7/8 times), 12Ts jump (1/8 times) + dec ixh + jp nz,loopNS +exitNS: +#ifdef MaskedSprites_USE_STACK_TRANSFER + ld sp,$1234 + pop ix + ex af,af' + ret po ; Return with DI if IFF2=0=DI at the beginning + ei ; Return with EI if IFF2=1=EI at the beginning + ret +#else + pop ix + ret +#endif +branchNS: + MaskedSprites_NEXT_ROW + dec ixh + jp nz,loopNS +#ifdef MaskedSprites_USE_STACK_TRANSFER + jp exitNS +#else + pop ix + ret +#endif +;MakeShiftedImage (MSI) +makeShiftedImage: + ld a,8 + sub c ; C = X MOD 8 = 1,2,3,4,5,6,7 to the right + ld ixl,a ; IXl = 8 - C = 7,6,5,4,3,2,1 to the left + push hl ; HL = start+n*96+16+C*2 + 1 + scf + call FindFirstUnusedBlockInMSFS ; and book it (SCF) + jr c,full + ex de,hl ; DE = start+m*96 = address for the shiftedimage-to-be + pop hl ; HL = start+n*96+16+C*2 + 1 + ld (hl),d + dec hl + ld (hl),e; ; HL = start+n*96+16+C*2 DEFW address for the shifted image + ld b,0 + sbc hl,bc + sbc hl,bc + ld a,(hl) ; HL = spriteImageReg + inc hl + ld h,(hl) + ld l,a ; HL = start of unshifted image + push de ; DE = address for image shifted C pixels + push de ; two PUSH because we will POP one just before useShiftedImage + exx + pop hl ; HL' = address for the shiftedimage-to-be + exx +loopMSI: + ld a,(hl) ; mask1 + inc hl + ld c,(hl) ; graph1 + inc hl + ld d,(hl) ; mask2 + inc hl + ld e,(hl) ; graph2 + inc hl + push hl ; spriteImageAddr += 4 + ld hl,$FF00 ; H = 255 , L = 0 + ld b,ixl +loopMSI1: + sll d ; 8; ShiftLeftLogical injects a 1 in bit0 of D + rla ; 4 + rl h ; 8 + sla e ; 8; ShiftLeftArithmetic injects a 0 in bit0 of E + rl c ; 8 + rl l ; 8 + djnz loopMSI1; 8+4+8+8+8+8 = 44 Ts + ld b,a + push de ; D,E = mask,graph 3rd byte + push bc ; B,D = mask,graph 2nd byte + push hl ; H,L = mask,graph 1st byte + exx + pop de ; D',E' = mask,graph 1st byte + ld (hl),d + inc hl + ld (hl),e + inc hl + pop de ; D',E' = mask,graph 2nd byte + ld (hl),d + inc hl + ld (hl),e + inc hl + pop de ; D',E' = mask,graph 3rd byte + ld (hl),d + inc hl + ld (hl),e + inc hl ; HL' += 6 in this loop + exx + pop hl ; spriteImageAddr + dec ixh + jp nz,loopMSI + pop de ; DE = address for image shifted C pixels + ld ixh,16 ; 16 scanlines + jp useShiftedImage +full: + pop hl ; HL = start+n*96+16+C*2 + 1 + pop bc ; BC = backgroundAddr + pop de ; DE = screenAddr where drawing will start + pop ix + ret + ENDP +END ASM +END SUB + + +#endif + diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/academy.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/academy.fzx new file mode 100644 index 0000000000000000000000000000000000000000..a2a7f27563ce43c4b22aeb85c0cea9dd8368b3cf GIT binary patch literal 1254 zcmYjPU1(fI6y9^D7eOz$y0>+p4{prPy3hwllGX(UsSczG+fgc1ZzjTIC_(IV9cD>5qF2N}>Hq=Q*7=tCcj^`f8`TvN}?UB&PI&77I@eczmO zQ&>3;@ElA%4;{K+gx#;eo>S0Wgj*J&wG6E{VgDJ}zY5Q+!jUuZ^a^BWLA(poYal)V z@eznmKzs_~a}ZyGC_sD-2OZq=4a}^AT8EkMKwJW`3E~G3KY_Rm;%5-Qg7_W8pCJB% z>8l_Zifd3@hu!N@w6J>Zj-Z&w z<)hen5Y!GCdI!~esNTochj_!s*t&>EFXEmrFe@-|n5|=S39}I8H-{zVTzu$JSOfGtO4dnQ@v@s-(Pd({r5Gf-$Rgqn6gP zYE-1G13Q-uXrCk>>Pr$SNxA+|p0%SO5caz=c++J{2&%r^ zqNI30J3d)l+1V%xx4k_cja+|N6veO~6lgunz8@Y4GfE&%LsqE*0Ypm(3iOsmv9Ymp zr3k}+LsTm|sJ3IW6+vH3)#SX41X7l=Cfkt5upiVHTrHXUMkijI-`+p$F_L`G| z#vOFp7ziDuKy=q@PAX4<=z%!ul9EcW%YB|UL(K_6IMgpC+Sqk{q7j|)Hl2@9uFlux v9JD9D4AI8w^qQ0INAq;@nuCaY&AK=wT=%R^Ii&1KGIpN+@kt;U2{8WvF+WK@ literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/belegost1.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/belegost1.fzx new file mode 100644 index 0000000000000000000000000000000000000000..f77eaf27b0e335d782825671f76b5e9029111b6a GIT binary patch literal 1310 zcmYLHU1%d!6wW<23?o#zn~BqTs4%1nJA?|eYbs$4%iYYlgxH5^EK5<5p+*^kVpj7I zHIzYvhNX)`DAny|VOp{!#)45q{HbfiimR^B?XGTHK?3?D2=QeJ7|=VD77sJ`=ey^g zbMAM(d(o8&La!jQh=Mknegh@mLem$}^hI=H35Dt?^e)m@k={V(ub^ixqw}jMyoO>M zX!3m&`vAp0MzPOO>>7%FgQmYju^T9M6P>w*&fG!ief0P~O8<(49-96gaepGNkGQ*t z8zK&Xy9dPkU|fK(1ouAxVg$tFAWngJ0>puR+L$XcgXh9inf7Rs-Q8Xm5kC4B86d254&_Y=GQ`$cG?&0^|!g`z2^!f%Y|M z--7l%Xg`AX6GU!;b_=whLB9j(eb9abtp}5TK;SQ!{2LzX!}whg{s9hQfy3Mw7LH+V z919bei(uh6<{retNz5g&Krwd~3y)!L79Tr@gHK_+fH?~bFJSIvER->~h@pzP3m7|? zTfxF*%&lSDw}72-oV~rwFvC81;0~05ynUU>GfT1|&(%uQmMLYPl*-;OdpkoOutPS; z-7dBJ-7e1);-A>^{L*rB+aj{*GGDhSP3gfT9VTb0e6Tu`afP{o2oirEwin}=4vTusoid%%Zv?P88(&sfj%3lZFV|DrU6=B-rTaw!E!6A zSM+>cDp%HXw{yD-jifT?IL^AB>{T`jg=}u2QDI2KLm*|fHWfwbP$_C|*4uir@ZRh% zPwXz#&6Z*87)GbOln5unmQ!@2FHKqcN2QixDVF+lBAY$PX0t!2^EdR{dS*pR6xP?< z%Vnz>UpV=^t0qfrwx2s)ZpPdBc;sMWDl=SpH9S~cCWIKy=Xx^VjOLroxl{RiD3pst zrEEJMj~B8DeM6E`QZv1s+s$pyyOrlwgM39!Z}t2h*=+K-CxqvFj8EM?IHO~>sPWrI?)ODQ!vj%yOf9JxBK9bhZlmIJ02&2=f; zunVr^p`JAl>h&gz%C0d&O>fn+Z5GBU65{SKkD_f7R)QB^W-c9r4KA(KS}lXcb6p=} zDf_H%eU-8{Qq_xmWL@?!rX<5nr7vAwyLu^|BCH&*>WRQOV literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/belegost2.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/belegost2.fzx new file mode 100644 index 0000000000000000000000000000000000000000..08c1cde105bdb55319d5624450508ec20c2e9e93 GIT binary patch literal 1358 zcmYLHTZkM*6z#e-1ri!vnYn8#B7wyv9}4A?j{UKSf=Ez5@)HpeEF>Qh8|fIqPUx&5 z3Yw4L0|EgFuAtd~21G#-gp?2mB#o{TmnIp_C}JWEu@#ht6oaR_XSJBA$L;$#=iD2L zlZB86#LX*Wup$4}vd>dwvv4zleq3M0`~&Oa%Wa_#eU71qZ~6cnk4O!~^16iSMBP z0u8ni-%Y$k^Y;=j6F)%w5b-0#yNGuaKSBHy@gACghWI(+{lq!-4^jLQ^1DdDZWnelzK{v8)c6r59BSkNwrm~MXBzRYDua@s{3T`L8%^=yRqYUq&g#epGft&RA;67N~-fxeJ8D7fwfgt zrPHbZ>B41+YxMoAA3m=4UKrevq{+c=&P@**t1~mUc5E`;ZtXKolj$@`Q>~rT+8AS< zvo?&hvAHWplhWl|%^3E&Zd_P2_>412Lf0gDQ&rAI2!P2od4m)F&V{!pnK6jZbG&i7 zA}n$M1yW;z0O{-;Di}TIT;SYc+>AYpEX%nG5IDIovaUd2>F`HZ{7}G^)*>MuSis?I zT?0^eS`XNcP|JVxE_IDU03P{OHCZ`oz+!|XemQq!cx}3|F&viVWHK6+CE|Uk&+i&| zwC~Ej(7q8@fM@}tUMau4xA$+4JlDhFaARY74f%4dJFCMD%0`gZIe^8&ZgdpZwNHT^ zdifi=Q0xI>=L*1Zh4o^3(m>MWVc1#W3}H;YZpUM5J6kKQcP_y!@FlE`qM&8%X=6H? z|7q75rUazBfZW$;1#KD65{BV=a}`;-QSuU+y?INwBMYh>(YCq1aNA(#k_=pvmX=PU z7Rlx|#z5o-1DICJD_1UEzI0_}InqH#D1t`Tw=zv;mI|3REgg(Bf&O0pDhoPMstoY` zK)})gt}@VS&?;CuG-=}1hpMRcAJE8i@vVWeqa2|P?a-J_1+{1Gsjogd)4omaTY}^$ pgOw(;$!UrBlx-e=*)}&*%>(fL)q3YlLA|pL_Rbw7Tzob}{R_4_&zt}N literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/bigbold.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/bigbold.fzx new file mode 100644 index 0000000000000000000000000000000000000000..3f5ce655675833082db9620d2428882d9ac001aa GIT binary patch literal 1450 zcmYjPU5MON6rOv|G@_)Xf|U`rP_+{UQEsO@cYqCH<@h@A@|G4z2|)2 zcdjc=4n_M_(K{}79~Xl+ME5PxeNP;CU-antQ1niV{zqbNS-iU}7S4(jXNC8PP!~l1 z3!yFv^^H*93biKGNT{EL`c0^@I9!M$e~SJ8h(o2=|DWg!=(j=L0cty_yFtx>x)0QY zpdJA=3ws}h{^QVp64X;Lcox(Ppk4xX6x7S0=0Uvz{UN9mpx%VRJD`?e@Bye(&^rU& zbI|(~`k%p>3vl2f=r2Kk1^R2ymqC9A`UlWgL0^OCeuTMSVD5L&*Wvj;K>rQ;U(h!| z3)EXs-;Q%zQQwJr2kLuJ??ins>MqVcgnRbj>;U&FeBd$ko&BIfmW> zIQk&PBZc65?g_zDMU8dOxFc9lgKMxq;nwd&}0gcURll z+4gp~orl}GjaHs#Sr&%I@Ds|a`nalQt4gM6){!y@({w(|7OJdT2!c4sV~#waha-;< zMs!3*2PH3(wv;8+bsX2_({XIf>e|}cuf`OGsL|~q&WzR!sSR)DwmGRq8z2EEyrgjll3~d zk&Om$Wo~WT(vJ-?qRxy~InAnw54mXL*2Cj)==gtv@!lLBrQCPgjupJM4{nkEwP3aM2eDyr5x8* zST)uNYeQ{9R%DF@n+Di@te5I@6?(Bfw6M&?l%rJ^O}LS41@rcC82Wya(6ynjMky`x zIUOgJ#zYa#I0&MMwYK=hmy7OOQ9N^MGFe%_a%G*2aXT$bo^F;!^>{;nnyt~TS!Ppb zYnC-;+EcWNtpJ;4c{D+bEj^2xc~EDTK->v!j?40~&ew=4ka`)k8egY~uNqZr1~Ns| su(Ca@V>eNfG}WkFN0Q3MQ9f7Ss+&kkKO~PS=rS~jF;!@?Q^eW-0dsaSV*mgE literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/cobra.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/cobra.fzx new file mode 100644 index 0000000000000000000000000000000000000000..e56c75498f3765f812bbd3b0536e9161f8fce064 GIT binary patch literal 1332 zcmXw1UuYaf7~lD3?_4C}c(;4KLeU{cSPasDFC-^95` zYy<2P)b5AcgHW--IS93f!G07DxKMinoJnwIz&Qd=8|HD_q)NlK5>QiqnGq2=eO`2sC3QhJQim#KM@mR_T!H)#1RrEk&FMQXlF z&C9g(AuWGQOP|v6HjQ-{u}qSF5ajp^c)pptIq&s$IA2&_=K-JVo)9q?x>WF3a1|oP zO1#8{r>TguG_@?o@B#}Pauq{1c(|!*vzG5$jGs7{Cp@yOB($u$EBCBJMZ$tP=d2zgLPJV~kPU@1>vf|vHBt;*bR&XiMuvTeI83?-Z17WSpWKX3G3L7X)F_iO zlD^Em+0{5km`wT@GRviigphf$i-Ed7j}MG|e+3Ih1EFJZ$inC*;ZO!)Aoue!Z?_j0 z=jS_je_UFco12}zvriN8B?1rhc};ryEah-{y&KlMy{-JJQh`VKj*1rUuIn;(x6|p& zPmhm}d_1`RNbiib_EwRUSlt_F$z$Av;G8kb8Ympj2Rzb{|2Cw+rt^?RNwS=ZNMdY- zNgO9(UF9$vCK;Z1pbEuc3zZcSDq7SIg^^+!?}}Z-saQ~?zt?HJ_zZjO?BZw0Cscr; z{vV0KvN}bqwIvlm7>6tg6_P*?^;JFU`ajG0@$o)dCFqa*3AfGyo+Vr)Spz?eaoPpV zVoi}Ax+o1cTmFS>SFT;~TXk*}B&pD=kDRl0Pc=>yIiWEa@@CvK&P)9C{q5$Wi17L9{G#oX&06{~U%H z=JDn`?|r_b;H@eMZ-Y^VQ}04*0g{W5x&)~en0y~5KY+>0U|#|IDqOq@=dQrTRj}89 zUW4(EfZhbU0rWP|O`vyxegX6wn79l1Z{f^6$ZWyP7G!)#-G>wd`T%GH=np`5f&K)v z4fI!_T^Rcv#`b{z0rWA@zkv>bB2o$IQ%DaXJ&g1jq|YKfigXMqMS22{KZjFOXeV*% zc}%{5*G){FM(0JGID^j1IPnTPuj2S?=;U!?2B%)f@gh#RICc)R=g}#l^9DL^qBDoi zTj0UZyW_t3e7$rUs{#N;ZTx{SxyQM-=PEz~|mX%n^2QTiISyC`j; zb|0nhQETAAA5r@mrC(6%qSV9jeH0(#fj&woY6nH>X;C{YO1e05RFsa12Tq8RA!;d6 zIxTv92;SP-bbp#p5^jWvkkofnlBP33-?=gdalRmsMhU<-P62 z<)Gqr2r&$!Djm{|ZrdEu6{(xn6Vru(t}nR$OHvT(hTCVY%7Bd=F%84C%jO=d7HUiL zis^cS;8}veb-AQPLLua<@)^7&cOs3nL z&00;r(b`(|n!fMPGT&SMt!mr$fHCHF_^>*z>k_ij$>s_Vn+?X>@qO0b?wae1j4glm z)yEGStlVIW+vn58Y_`Q!imQvu_2z}yY;K=lSYUotV!Z)VRbAH^+upYAwBE{|r6>7m zef_&Vn>9*CzRhxtvq@P#vd0wN=(PXp3;}j{H>%7L5z--Zn|%AnwD&Em-_a{K%a&Cr z2K8n!=LexImmUSRav|6EL&M#-c-iG%P*p;nR0x8=6^8v70%xtRaRuJaPQa6f(em#4 z?rx>F6a*!nSt%5A^`>8Xv{Nf9&4Bk?U7AxuG88QtK~SGI^%gfFsNMc^I%pLhw(MRx ztyFq7-R+sgC&V0<&Wk5HFFU`H&fNI&lN*_|q6$%YqjHQr@jrp+tx-9~#xtVsM&;Ny z9*9aBYD9c_AbL2V{s)df^Q0g}jgjg^hdRzJwZo3@#f1NvN^M jTs4BDsIl7#H71f!V{=u0DZ-;BcAxmnFGT@Gjg9{omonFH literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/d_o_c.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/d_o_c.fzx new file mode 100644 index 0000000000000000000000000000000000000000..e67f4edfcbcda20fbb98810e230af9f34e621f20 GIT binary patch literal 1169 zcmYjOal|E66rXc$vTIje?=7Xo?Q63{y!Xx{)@JX{JSmCMCQD+HX#a@W{@S#0S@D!z zx80Tf*y?VIm#9~)ZPgz`tV;Am`6!tsmK0Z({Y2?^XTD{RnK@_9{hiFHCD%YFlTj=2dA0WdH61?4|wnqt511&fz_8h zY*=09d%t5|vHF?SuROTI>M9TaWc4?zO}^zn9xA1_tKn@*?NSbwxe#=@S>my#4zVm- z`^{=p9Xe}+U51r)Ho@Rvt~8sbG$tB@pV&0pw6U)C%aw0!13+7OUAb6!qkUyU05G~% z>$Y;O_f_=3+sapn@z%g*nX_32HV|18vxqM!=h|du^yt~e7w%~7eMEr=19)I9BE+?8 zor~Sq)ozIlvJnnKY&+6gr%Y&qPetI|*jOy)-md_y0R;nI93TuUUEwGA+C|9S3g#=3n)!UbSd`j_^xsL3Y4+G8>IS0qwkGv$Y@1v(C4yLczbsu?fw}^&)#VbkRcKsg z1IkkGw1tH5xC%CyNa2FAWQz+}r33Ha!}-P#38Ei94tt3!an6R*vhG$#`MjrO&^-SR%wbazo9 W=h*cwsvV z3)K$v9zpdeifL3Fy`89bq4y;2dpph$F>CACj)%rLmr~0d zC$eT7r;5y(Xy8>4Pl-=cfjJ_1uE~tH$W$BETB(^3IQ{sMSF%)p zz_JI6ft{+wYSNaHrfd=$^?4$*s(EHpwmhCE-R^))^cf>_Q>Mv^(sq(4&&d-pySq*u z8xFhOxw$-V-O`>ktDUdp3-x=E_hq2x$$%C}+3k`_vQLFkEN5qrotjt5aos3ap-W!} z^)0HP-dxJ&1SScWEct{mGMdn5H!RjDkt8fiH_VDy*MqP~+;D9pcEge)B|^eC8b-mi zm}E;+b7{}2xh!Uk?y)hyGX2=0<;&}fsmZj)9_rdmldrQ0jQ#vSK&w=nu03QQ*3gCAE{t)2f0G|ZWuK}I`_y>T02GMzd ze+Q8Uz<+_rB@p=sd~gLMu7WgxI1E$Q!{|*gwF*Yoz|Cu6G!7$oKzt9xDTuegbRSIJ z2h%!CY=@}_U}^_U4MDscrXGRmEKENNQ+b$v9BwYc)#Grb16Mx}@1KCtmmzu$qG^cU zhv;L7PC)b(L}iG+gQy149PBy=(FGV@gyBmtd<7yPcU>oU-7JTrGFmI6+vV_`a(JVR zw#cYoM%!idpd22S(Wo5evZ|_-G8QV=^=7rIkB^UEb-3^7`U69;NG;>lW+&A#r1=AW!)l*z~EQ z#xwJciHVA5`Lm@-fvC}ps_Uj*nW*_TwQIGb3yv~xQOlyC0Css(tP5~4rQ?MhkJ0wS zWNBJ0vJfj0etPQU$v11PuZOIUbKf_N#){sCG3X8$-1o>`hAvn(Y&+A^4bHjawYIgk zWvtvT%kw5G3x2hh+{`aMt(heR{A!}heihr0_K`fyBh}2<%Li`b!xwvWA}=hVOmy> zA6I#fY26KOs^|TEblp0(&(Y+;TCF0^D5Q@X!7^3aQ#tm=+=6N5biHQe))UpNoNoNF zCy^vQJ%p$i{OTm3w{geU0wV)!-Ev*{N)$ywnj-8I22z(=-skQ2%<)vytXqtEv4l}u z*zYpa5s02PcD~NFwJ7BXmp2L+uWYNLm9>InU3;;*Xe;wY(O%fUhhJnNx1Jdg>_{jk zpp>eqx{RAe%ltdghviHIl!!*>HZ4+r5vmsIN1@!XV3RyNzMM|h6KT?*{+o0DprJV?h1b+6!k|S8j zc!m&0#8^OgR88J;n)9J{?8Jc`jgitxoA^_4a#_(NX@@Mx7)dVaOY@sLaVw<7j2+Dh JU5a2K{V#+nHT3`h literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/hijack.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/hijack.fzx new file mode 100644 index 0000000000000000000000000000000000000000..e365183dde159f60a95127c0e1649b0e8e4e547d GIT binary patch literal 1270 zcmXYve`wrf7{}lDdE@2vJT%<0T+vcrvTNSp$~@@Qz>b>5n8mH&={BsgL8)UDk#W>A zGVSKe5lX0s9=AH@sM>A=e=)=|{}|{vor;3~p`ea2q4<{|Vqt%Hpj6+aA%x_8-sji% z^E@T=)*Qa=1g@UM!zZzw;L59*cQHSM%`>=q4p-mC4=m%I=kd%1d|(C7EMxl;<{#tX zPcXlN`Dd7aj`>%Ze~bC|nE!ySpYW4EMf}B zLEV4~A?od1X1sLv8(nyAN! z@d8m#5KUvuMIyZ3DWjKBRX!p5SH8-S-Eee!-JQDljb`m_*TUM=VlwOYM9p=D5bmUw zhV~k)&l-%ayKb-NiuGO(BR~qGNJ9+C#Hb$Z?Oyyc8n7GEy;QLP0t@#la(f9*pBQY(z~TNGEINZPm-~$Y<~naG^bDdS5$-On*X%7h~TpmP#`n#)c-(Dp49D<|99pO6F4` zsf^GZ(WVn~YDr0&WB%D~b0v-SWbxXja3N|`lP1=i?34zPi5^1-(F*zib&-!8RMJq^ zae?L&v2#OF$qhvXWyLc?N>rCZQo%(fAF1@ndemu-UV3Dm=rm_qplGwf0?lDdOGc0= z`IbmShKDHm`0es0P@U#0BeQ0Jbed0JhGeh`G~WZA=7e;bk6p91j26}J+gtz{lNky$ H--!PKD*{vr literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/italika.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/italika.fzx new file mode 100644 index 0000000000000000000000000000000000000000..a252f264b6172fc19d799f83929fa87e62a1a044 GIT binary patch literal 1234 zcmXw%e`p(J7{~KGmtJCw(qha`h*`U&?~733U9O9#JJVUZOO^Ty>zsdxM*>Hwp;QX- z)N59yV8wzHm9=%kCWGyd{ZTy^3rd9!D$b%%C4#~!G4s942?y`J-}iam z=lgt~_jdT$0(ceVc^I6B;sO*Gfh+-8h9k?6I}5oFfULmq8dN`m$yIoH1;}|I7l2#> zas}vBAnQOs1NsG!4WKsACKMbP`x^3FaB>Us-vap#$PYkn0QnKf&p>_!`Ww(cfcypY zZ=nAI1*APlZbNzp(mtejBe@63y+{U-3?jJ?>HSEKB0YxWK_m|&eFSqSkvxjI$8q>6 zJof|^pGNH&B+sGtJkl3Xdl5+$%LdYE%+Da1L;4ElU&r!1YG+V;1Ivr3y@lnsQF{mT z@1ph|YVTwDLoA;|Z4Jxkaqt2TT*ARCNG>D2is?@=_Bp0EFm?^oU!k~#>1`Bu@X!qu zf5Jn*VC;8H|B2#1n7)N#kB~|Tv0fpT7Q~E@9uQ(wNDm3Ig3zM~Vo4CkgxF(3`bi=7 ztiYJAM{K(`em398kGIF8r73I3J5e}QlZw->QajD+yLJ7<0@H2V)*1hJhTS`F)|Gb`(s z^g5ul7%MCRq{BUxEreBf_jVzx-rlp^+()#+bXr7fW;6 zs?oIV15+6?G-ug6T^d%I#WfexiL;t{6Ph zFecoc^}Sqf^pth5q)jMn)?KVrhBFe|8__tWN~gtP+IGwDC@Er#Gi9vBOph11l_@e) zZNtyeSSDz|w*6l&)OXd<)r%KbM^!N%2`mcHkbWu@68{T{ zXe20?iih-q2;QMD1mojE;ED@f%seHiors3~c$KatF&={DN`Op^hcAM^kpPGg4M&}d yhX_;g@O|()0h2^D9Ccux^AV!q(0OPVN-7@CIL?y-Lea#r?zp>NTuX?C^ZyUjQLa1y literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/just6.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/just6.fzx new file mode 100644 index 0000000000000000000000000000000000000000..709689c254e80d7f3c17a4c910072de930010c69 GIT binary patch literal 1241 zcmYjPO{g425Z>AUjTCf_&eYqfG-071pEu|Z@|9;UjqIM_%iU{!2bdR@)gKeA@4!H z26->?b;#Ev--vt@@;>C7k#9x54f%HDJCW~3K7g}(kPjl?hbQkvK7{6e(i*tV0{kNd91%d^*z=XQT>educ-dO`cG7svHk}Y7WI{d+Edim7HV3k z8w$lmy{}NW6!ol7cNFzqg*s5EgM~U&)DITwk%H2g(0o4cn=wJqt`wsNhV^^36bmRh-@Xnnb@U0zCWtL>(bRTbS( z%4xrLgZDP3Fs-d@<(021t99E>S{X8tPFV#qPGVXzgoI4U`fz&3r3fFrAI8^u=bX3e zUab8(IyX2M$HjGWxA5KQ?}c|_Sf(M+{c?BNr(hY;{}_U}dA|ya%uBX5A;@LI6Ft&N z0tC-W_?V1R$mlOBrc(%^@xCFM(vksF39*xsweC_yC1VYXp#gk zcUDqaC#4Jtd%i8!EL0;!q!Kn2h^vjlKTe5uiDb@W-0tcSDjTIM!ACo#NoG2wO%s}p zq4}R7BR0~>EMoSMLbD-tQ4E<_L&7$xErtv)Ir7gYJSLfHDR>)!6L6AlDrxhdA%<3H zitnc74Q-@sXx)oXzs^9iOXcR4XZE7fm_#;P)hLW0=aPveCPPyb(wclnLt>H*{{apZ Bwub-! literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/locomotion.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/locomotion.fzx new file mode 100644 index 0000000000000000000000000000000000000000..e22bc25de4d40d0145f8d0edaf5faef1914c9cf9 GIT binary patch literal 1270 zcmXw1Z)hAv6yJG!$*nc$V5T*7oSM>mqf23oDCaB77ht!}E6 z4aCbPEOsrmL``X`tz7z{lv*Sr#Xl%LXp~fHiwIH?%3=Iq1tn3zD}=4dD+zCbj%swz}F!z8t1m-ZbJpty^ zkSas^MM%8_`YUkkF!Xv*Is&<)AbiM;gQ$Qt1>z0J9S7?qbj(0@7Ob!Tf z7oc`zC0oZluyb0nabo~JOZAjb!eF+kGLH`q) z{sH|SB-SCFP?K9!<3TlPs7AZmY^g?1WjB z^J>$GntWL`UR9H0Y9LJss#Ryz!G|@fjqV0pSfp&|1asNG=rtZN9#?UO4;l|#*1%ur z80SHwR-;s|0{=u9h6{eVidEfSL5M`AJhkdYlu<46@~%7GpfuA!s65669?UOBk+&LZ zT8xl)oV*i;z86KaBO0v-iV|=|!4}w(vt?x&JrDdabSPD*KXduZ!0&%NwbiohYn932 zRBLN%#fxI9k%uvcGG313iAd|$u<3XZp8Yiu`bXZe`Yqf3yC@Q9p^xA8goyKcMo+bl z6$D4KSMR;Qefy5Rd+&~(*_O+7cCHldNi@4ynY`}-P1E{i{FdEUnQYyvr_y7YES?qd zCc+rF34S?mS=J+u?p%30rju&_dT{Wm{rit*mpW1{ntuL;Z|rP_GRIq=N>nJ@y;hIu z2SFV*;*3#RM=8jkmQIchq4@kWgwI4DHHo5u5HG#_O<ea3m6^V)`v z-bR(+N{w?~<_dbWpvK*D*>!3%D*t?VaB%Q8qKVJ&EyOFI^B8hOYngYksKE2la*WH% z3qlO-T67$j-4Oc@iO6McwCKd8UV}2x$Rj+~<>g7x)$kxlzLs}_qW$dL+Fy{dG;M*|F9_l}%nP zR0^HV#EntAAvGayj(sh^DNGBMS0VJP2R5XPVTW;MYsf?@^PIc_D+nqzYS!fJ?V`&z gWZ9CKh=<+;6n99CH2rrRKTaR_$V9Ui-v>fGM_!Ri( zz`q3k74UC?e-Hd8;6DR5u;&6icoFvg0{ec2y;p$$4vQ1u8Sr)Be**s-cn-V({x9$n zdKCl?@@*K*BKI-48TlM?hI}6RoyZq3*ok}>a)JC_C?r^&c?#$h8=N{KvbiMoCnLTcI;ClPr?qja^ zgsbgvv{DmQ*LI4ksG>^bs}rGq%j9Lbm1QeZYT0DcYKK-~U584DsuF^ZWvMk|%~prX z7^6b5TnHh`q1o`+!MZ-dBF_x{`AF6N?X|89o#utCj2Y4$Wr`tfY0`JhXUmop)p7`2 zP*G8q4i%BKZx9`poLc?ycoIHuOdW5PX<8anMKO_#qe)H-@=1MACX!0qMXuA`H4{g4 zQ`1*i6|$&CjIqhGNhif5HS1R5`O0k8q(yGlO)w?;`_>mlC;vvvqz*XZ5y(5=&Mj`B9vZ}&bk%FoRCzF$U-aYQd8@45no?h z8#if5nx@Hj7FRb#eA;Ur$>`*eiAu_)a4R;KR!Bga^LY3CXt^4RrOI}k zs5ij&hxQa%8b$Hi)vIf9#Drs64t-X~a+zA7!fAkB&9o;JXzHo3wXn$`kv2exR4{ai zR+*}>^_l`raTT^E`plVf@>%OHMH-8I);UU-YN86;H$te5Cn{{)O&Ev5YBviO@pqmsDTwl;)zudWN&XX94Hdnz8a*+7AY!7S;6Wb{>Wrv2a0fh{_^MF%bre^2}a@sGp>@lV7TiGL=Z6aPYdiMqcL zm&AWi_$TpS#8-*05nm_%n^+V7OW_S-0xy8~f^Pxe25w>b4k#bXZG?B@>|Nk{pzcL@ zAJqK__dz{`-osFjAY4MXhvgOY9!2+Y9Eza!Lmh;A0_sT|JPh>|mXAO^4RsWUo`HG} z>UpSR2w%X`iwIvv?H zNoMWD##TUWrERjE+lqOf*Frqcn@&Z6MdSuqrp;_NpU+6I+a zxn8H!5m*38-;PH!n=8bTyh@UMHO*0(nbKA+MRLn4gJ3WygW)-GH^$MeY-wmLatwN8mupZtSeCT0r#K-XgCktdaDaH9c>~5@d62 zE#)M!A{E82{b8jyO^1b?RC>sh>6M`DbXKZqWM!}-a{C+OQ(K!~Y@Hgn0;dutz8-9U z7ZCqHU|Usy-Kk~RYgwS$%SPl>itLAKoHnqHEu)%OD{yK(64Y~)Kq!S4UoSe^9fWoE xdi}tu=O#h5ysEFg=}jP)+-fx9>&1$YdLTlnmnna3HJnboyI0{>fcSdR{{cA13j+WE literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/nether.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/nether.fzx new file mode 100644 index 0000000000000000000000000000000000000000..4045e4b4b15af1f60945c2f8730a4726a9953f00 GIT binary patch literal 1222 zcmYjPU1%It6y9@2=Rt>1We67$SvTx$kTp!Q+Y%%M8YfCI;s`}Xd~g&kc^KAFq(nND zC8E;$gD9=7QnmU}P{dFLLqxrzNFG915W=;FWmqIEVYn=UVX4FFxigFSF>`;;{m#$* z&b9Dz4EPdE$8h8{96bZom*L2(z|XRV84!}t#H>%cp}yTJE=?*muB{{(&j zl^ei^z!ABL{1)V6$ZtnJj{FYfcOkDJ=g99tK81V+`BCKeA)m*}0@fF?avZA<F+P|Xt z8`>Q-|3G^m%|6Efj zh9HzgD3{dIQfJAD`zv#E17}OFHNq{&zZmVlaVikyK+}S9O9TYb@}11_9U2@mw+2F3 z%n3xOws-ba==39vl{92gmMy2&q?ImPwNNR`8j5sGscSQ+N#Tr$Tbly7YoFRlXnYhK@`%QF=YOwk#bAO{`V{5d;yv1MlOxg-chi zTw0i8PKoN=j4U>i-5(ih|1;#-YHTE1!H`jnjr!)5OLHqw_Ed2WhP2uE3pk|>Q* zT2Zp(Qdw;D6oNtvCn!3cP>e!5-x<9NvgC@?*r>%ihE~U7MW@2Gpj2p5W24@dy>hP4 Oe_bY@+>jTdJN_^Fdsoc> literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/neverend.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/neverend.fzx new file mode 100644 index 0000000000000000000000000000000000000000..1b598e042d4a7db67f2c77a06699cd2c9c5f93b7 GIT binary patch literal 1266 zcmY*XU1%It6y9?t>@2#J723MWiW5l21d2|xyK{q%>2_1Mc?gKmOr-Wc6sqLg- zox(6Xn}%)GShc2*#kOf$3>6|u{h^?hY11?+O9k;EqUiWTR!~c*p!+b)dS^HD;9;11 z?|0Ar?m6H6qHxZDwquYqAytI*B&5oaei_oIVEgNkoPlHo$a&ax0Vd|*r8$^r&c#cR zS^)7~5EnswAH*vlehlJO5I+U+bJ%Ia&Lzk$!=7cxu7FsD^w&VX1@aw`AA$S|N6yC$+6@2PLBv+BYhU_x(Um^Pq@~g-?*!df>e@r<0y(EgcN0Ub>NKwcYuCe{X)H~&fWYX#tlwuCpGR_Yg)JMj3o#e+qNCewmCr% zG)Kx-=I1L}iR0=V=O;95I}+nEmhsBz)1JXO9H-Td^4g%_8I#haXC!*}dU46jxZ$#4 zT5h>)csDvCf^JM2bk``G6u0dVy~O`zC{u>3hwAiCIm_Xw5E2A^X7Mvi?;8@gWwXE3 zj0JsLzEJR_{^L{D^-9jmq9Q4XL)E%D2m2vGCS`vF$yHc(!zn(lylDmq(=D z!Sc$|d_LvpHJ14313iU?V>N>gnGDCpVpKXm98SC3oVdNa)-xG5VpOZ9qZq2{>h?{2 zQ`BGURoB)fopWhN`VLjyalDnXCWJKM13eznuPMHET ebj4aC`4w!udyJm*HH&PH1Qwv@e1x70kpBSj?A;0g literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/roman.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/roman.fzx new file mode 100644 index 0000000000000000000000000000000000000000..965e28676df2da143fe1355eefec17177208d0fa GIT binary patch literal 1197 zcmYk4QD_`R7{~AXwij1CbkRL~Xt4visf!})C3|vWC}W%piv(Hls6j*yED{89s})8$ zaY+vyG3Z%?O~B$doN!_(L4u@Ea32mi#D_i<>_Z<8DcA>vLwwkWh<>-(fXnXA?#Imk z`_Jz`CAioDd>3jR@H$Zc0K9Wh{}AdQLrp{N6R3R(vIp`!y!APp{tV7!zup(%eF@$c zcwd3{HF(?LeFOCYc;A6{1;h?K^F1u?LTeWme}MW`s9yt~03QM$0sjT~b>KIE-voXO zX2-y91HS|O58xB1{tY|@z7HIb7xCyl$nV1w4f^AH)gnDKW5Ch zMv_EHAsO|0{=QE)OXJi@*>GgB<#iUYmOd*Y5z$2hT~WYND$|R#lQc<8i-{=Q91KQb zJ7!c5HC58obSpZHqBu@j8cj?chS5apaAKMZrWxvxzUfh>8AS!s76p-zkYH&crMHMw z*R%?j+PHizQtrxNr1e?V@9*`yY9)?i8q!v)bwj4~w-?fVnom*XH1@^)oF%UdnI;}5jf*6PMazuQ$i{xYeEm43gwu^yUx zQmVl3!bzzfxqrJ8t=hJ|K|-`p$~hXO<5*fwg`D+~>pIIrEv0Nwv$YO^>8y>X;KsBe z%N!FNHfG^~hJPE9`s};D(kc;3%01Ule79sOLeh}?qR|kcNJO}ymRvhm3N_`lLPPrwvAmjj3%i&MIU%h!+;OE?wGMTqs$E%v^=#)SbK#{I8I;vhO}* zMpLrAWmBfPQ6aPBSUKK+6|&Bbl|!qr!hxX*JH$z)3|7g?&w`uNa77++zz*m+R(>$3 pPX;7ldB&N0f>Sz7n;IV=ajZO9I@lQ=7EF%^WlVjuLdnWQ{}1`Yn3Vtk literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/script.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/script.fzx new file mode 100644 index 0000000000000000000000000000000000000000..411de344323a56b773500b409133cd7c17dc6884 GIT binary patch literal 1230 zcmXw1U1%It6rOWtvwMXom$*q+iRIGFUu)Irl57`TaY%PIV+>&$w+d1r53%%N8^$O) zVhCeN(oxfPx~`Jc2oZuTYXzaDq!7Y_C6rJUL`6!KrGog-hbRamq~M+1@yu|~+;7hB zcTTtP`79*fg(I^t_#WiuVCXF579i(C_8eqCgzR}RFM@dqW-h_&7h&cCm;uN&7+3~* z734LL*Fmm>dXzUt{tr_HJNu3zIi7xr4pmWAZ0V z-@#-H`~JYxeeC@kQz51wU{Vwhq{Oa=#pI)6>M^l*znGH5eFw$lGh$*$Og$_18DdvS z?0Zq{Ixc!#1TP4zJ&V6ATnn|3-MmHF*b=i@*?-IMjL3#7E}qRo4)cTYOW14@$_&r( zBXQW|8sp;l5g2l%6W{T8Y?so|+a>(wc)O8hNFo!1`$cc$?h?ln1kVrz9+!tSSSW=2 zG(2~nP=UI&E1RCHTUOT1? zuw3h*lt&obAcO=h+g_$rlS=&5b=}_J9ZX$TUYV#qakg4*n3ZbKbB2fF`TC{tnv}`2 zCS&<_7ha2Hc>VTTK^PWRgfIBB!oLavWkS2&ZW<56jsfj|AWei$R*klqTt3B3+Wl%Y z9w!Id)oC=QocnZ2NRyaMz7U<#5+r6h${aD+P2&||9eOe>9=t|lLV4I|UuMqmJZ*l` oB%V8u+UBa8#v5R>n42_-=S|Y2%@echyZ}D=1QFT*Wqt?v57nr6wg3PC literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/script2.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/script2.fzx new file mode 100644 index 0000000000000000000000000000000000000000..2ac2fb3b07de047065c9e5ef3dfd82188ff05e30 GIT binary patch literal 1295 zcmX|9U5Fz^5KdP+#|SxSHJuFz(nr!T-iuK#7(t|!%w9Yn?1!ubeQ+M`VEqBd^EgJl zlW{pU$e}SCbUh#B+DLhN$5|J+u`4W*Ylj~n0*rf-wFSN@GFF0 zCHx=4uK|876s`ka1b!pT3E&d=LEyIlza5Hq0>2yhJn;K~YnWSr;)5Uq;K!iy2-F^f z$`c@;g6E%v+6nMa0)Gbl)4-R&KLh+M_^$$g4JzkAz61Py;OF7+hu~iX|0D1}0sj*C zpMifF_$JJM3G-t({590J!2cHf@1VR52Y!U|4#=Nj?l<7O;9LQI6`a3-19e==_fzKv z%1hL_iSjaaZl?TJ>fAy3UDRpN!oAeFpE?WFd5Fq~sdJ3-N2wE1=OlGb(c*J--;1<( zmhS7(*hntcS`W3(@TWyI485sH$4sb|k{S+G(p^+a#X=!e348>qA_)^zG9l7b#aWuV zh>ye0N;2+*D#ZY`6L#Dx`pn^WLRQt?jIk`9y7*OB@wC$kGZUs%h$=q#MlRb)(_D&z ze=0Uvm0(#eGlwmKAvqCtl8v=E#GEMo#V|?MlH@`==$eMkL_s(4Ja2a!zk|Sj4_L(d zXh(OqA7P)(^^Iz{w%)92hJvmk_9MIyJ07=m80ywIUWpi+tVin47Rtf{YKlbZRy$a+ zRikKibz?YOomK54m>JVrGHTdRTX0~rrzTRjytLJxoi^pc#>ivL8#S8AhMTW1dQ4py zxRKb*rlyg2YPxo(n1ZpXu#}6e+T2kp!`AzYLhC?nw?tKAlZdg#stz{m){`E^YUsp{ z(P&J;q}5hArP5{ac0oM`XGH0tr+z2%+?5n9!QZeTT>lFfrfbl5GFRM0I% zhOuCHG-@`-M-Bz8elfq)>n%U-PDIdmwVt%npQ?dAU=TZ8iJu@Fci0-xDAurQQU#aEQ&3d+=*f2qUD z6#0KfTRXmHwW<`MmJ5+KrlCzneJ!;fG*TAI)ElkV*3{PO#ycwVbC2%#3u3y&j8efQ zw31EGP-NxQJ8=*f$4;I5a=i>`B&9UM2(P%h4Zm*4rDf8iQn9G`c=({_O@}4`9hboPxRH*|FL?-CT8WV4H z?bu`T@vZv*29S?8M6tgMR{slY Ctv7uD literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/tomahawk.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/tomahawk.fzx new file mode 100644 index 0000000000000000000000000000000000000000..67592d045054302f75c568b3c06ced27df9730cd GIT binary patch literal 1235 zcmYjPQD_`R7~cP1cOUE`r{2{?u%x-nL?p^!G_2*Q8gE;rh=Np3d~k@e7MzEx)MJVl zR76xPsAy_}B3G5qS-vPS@?0a~$2lIWHU5Dm6%>Dpu1Ne`?Hi2IUJ^=m;@EgE?2fhdF zPpJP5{2!Pe14o>8k==~^7UZ`fKaBiNWJi!qBIC&JL4F^y`;pBdJBIb+SbYrZCy|}P zr6-U*iQ!Yop26m6WY1yqd1NnO_#*O`vHl89okji{*3aX~*D+kg<{KEkjp4f(zK_ih zkbQ_JKE~!Jcwz~|&#?J9hO0QWhDR^q)Meyf;Pe%&eueJ0SiOdB53B3wY+&stbbrR$ z0Nr1)_8U5XVD&F_{>7SQJFb2EA^WCVZRZZ#z00mm+Roi}^)rj zfK`f-H6mIWQ`i@l==KMLLEJT$a+*=pjiS5}k-VreQc)DC(RksEKC>_`7MYpYMsz$W z@{`PHli3l^^N4aMQd&hj23&~7y`BC2tzIwL&9^B#Q2l;i1%`nxnA7#7V@_J_k$z25 z^RG$0QDR21Xf5d0=6J79KpD}Lk|)dJr6kWtW*pPMBqM!4GuK5%VYB9elB6VW`#M!a zsg+V`JB`zJKmawUosQ!6K~8p#0PSZ(sX9@#C#|6=%C`dF_tUgJl%}p51|&Gz-`d)u z{wjI{13`yaoZ>6k(4UfrSP1|o9J(Dj#W_w9f_@-QNPt!0@%mp2Ecy9p?` zTcY%lSPDs*S250$!Cu8OzMbpofJG&ZcCIcFT_*nUZrgQ dj9xp45fI^B<^B)-N78d{j literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/ultrabold.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/ultrabold.fzx new file mode 100644 index 0000000000000000000000000000000000000000..f7fecd4955ee35d71231ef38fa6c99754c748d7c GIT binary patch literal 1462 zcmY*YU5MON6rOv|I2UvYOVxCgxDRWZ3;Q6|hw0S~16rE(#eFIj)Q16a1qme--Lw)? z*NTWxC975AF~6e*@3g{*eC3jGwj$I=6=e= zXUzMOiLaTs#KiYZ{KCYqOk87P$i$y)?+v#1FShR%d*K$__YV^-5FHRRAa;Q0f|vv0 zf>;2t8$=IWAH)+No&>QBVn2vyKny^vfCxbx0&y6;6vP`K-Ue|3#7PkEf%pjAv*4Zs z_jB;p;pBPfU4Y;l@Ge8}9e6)Oa234YVUGfD1Kcg}Zi4qWxFswh1{{OCFqp;QUJUNT z-~sH-W8h)1fWbo;Jc7X<3>I?!1ZoU)%|8erLP0qusf`-I;Img|_{0 zn?Kr)2!qyOG|+nGhcGM(bA>WlKc76dzMjZsec?Iz@~ir~4CPT>#1yV%qzocSmqDaL zS-t5(}_GhHo>w;bjB?|Ql)vA<5`wcSeBKX^HQl?^KW(1 ze^Ke%8D$Y`S&kzm>13&4%)@rfbwaxsQu=!K&BoYQige!RW8xvk

+?dKpkVLc0 zq#BfK7qhb2%j6M^ae_|jttqHtHgBvh)(qW=Q7NZNsg*UV9J(3rgcZMQ7LvZWMr-Of zeCj)d61incFUOS$rOZl3Now5ajb~+2ix>WCM3of&K`YgohCl@n@Muta~47CL@Aw*N8?=AU+CLPX<%A!Kx*b*E!6L7 zO)YDy(8hV9RbuIcvZ82p)vsdflu*u+okCxJow8-EqEm`3s=^DOY?zAurU6QgsBk(z zauoe`ocrnU%1>8@sc&^#lx*s1=@Hw^!s+GJMyHHN2Q*T8YI;XAjx>Zv$%j!aj lPWa691ooWPq_^g;I{^vQ`AN4rWu%&=jaL$=tQsy${{u8G1K|Jw literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/upcasebold.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/upcasebold.fzx new file mode 100644 index 0000000000000000000000000000000000000000..7ba37c2bf702129d4c719f6cf42fd87c15966977 GIT binary patch literal 1367 zcmYjPacCP=7{B+uUN|I<$5OL^LoR793HZkj>SmCDXL61Xh6b#|KOBVmhayF;Q1qN5&S(p^r%X z650D1iGNPwD!>cg#HKf+z@HHrH zfO!K-n_&J3rJun31%`iv(iWJ1fcY1ge}j1!N&%#RstQf*p-=Cj`|hK$`>C9wu^g2T z(7}ULF3?z!%13DINh%+sv1e)IIXd_vl_zNI6)HP4`35B?Y0RbaX&O62Ba3wKB8_}P zX`RX|l(wk+EscFgdhZ{Cijn(BK8daRY3$`PO?9Us)wuo|i0}8`5YGJC?m=XwXmYoQ0Fp-46 zT9H&_5` z&uDdjy6e?_uh9)xkBj^Djkay#Z3E*B@pjMS1JP*oXuED9-o?;eywNSTe!t#owPuU0 z+yC79rZtPzqBU&f@A?=g^0#j`Ch=VFZnWnLwpX8w+L%{}sjDIn)**PsqCVDau5E5! z9>;SXL#t-HAv{%QTc~?0u#wdBkQ(0`jZpuE1X|Z-xyicVbAks7`b&X+z^SnEl526^ zab35AE9Vwt79OmM>vOlF8)&{^SkOmK;VVRgu=8bgSMMEq(OnZq{(=%ClGbmB##t2*kXN}-S}H!4O2X9Yt;NE8-A{{a@8JP7~* literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/wildvest.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/wildvest.fzx new file mode 100644 index 0000000000000000000000000000000000000000..3dc5c7880b3c4afacdca53b4b3ef3d580d468a2c GIT binary patch literal 1212 zcmYjPU1%It6y9?tnJzel5vDQ^i(Imqn|TN_`O|F(xGu9H4?*T3=%R}bF>Y5G!zR{} z(pXoaY0x!Iq!f!45|Gk|JVXc@ZG^EbV;^EDC6pl)9YS3eS;9UPx=0YuZ05m_d*|N! zaps=yeCJB|?E>Hh=wE=ri!gi%hCYMg%P_nI{a2v>bLjsP%&TCo!t5$cUxnFaFjs(I zgXGu1ZvtNf{vBwyfo}lc1il4)8(#St27ZFf9hkfWnO|V|SJ0}U-2=W0+I`^rzo4K(2%4j)rP0iwHiqUnCMPg!p*D$wujAmGsJ)HJcW~@o%$`N_JH4> z@pn`npy;FWCyIZe@;7!iG5G{L{=v?eAd152X9V$CL3vIP6GBI~(5VX@F9@fG1m%oy z>Se(t#N3sY@#AqCthLtCTdaCdW)l@=vB{-#>2kVEpoLJjSc_=-FgmncH!ibu+41%s z);uS$Lp1ZtU5W0ucEhD>IWP51RF0(Q7hdY1$nk)M2w3ShE&ry>W;%UrsX0Fv*hR;2>Xz^NR)PrM&xiYxVYhYW ziO8w;NmRp*1*DE_F*Y|@U^_*-*dT_`tiMmF(^ZZ|zgXd}Xa^_1C+bv`ovW9&@3ZjQ zqD~=xE{wCP)&2d+CExOwLMo+}{OwZRPch~)mTDJJ4j5BOyR|l&xv{=}BQvV9SX-kc zN9J1*q5qF)r_<%gyePub<*0kQ9BZr6<;Z1MHnZJ zs4VKV#Uc-NIeH%LJY8JPnAn@)N@^qOwr}2#q`d@)Raexx-Cb97>|hm0Ad^5 AssI20 literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/fzx_fonts/winter.fzx b/src/lib/arch/zx81sd/stdlib/fzx_fonts/winter.fzx new file mode 100644 index 0000000000000000000000000000000000000000..adc7e36d29d1857404c728d10b78d68c365d2aaa GIT binary patch literal 1240 zcmYjOO^6&t6rT6G10z9bNh>2LlrwL2Jp@KcU_B_cP{JsPEt-Sm;N&2z2cZHYqr_0i z9_p}26P9G+4-OHuQPdSd4+^%3ID$DShsYR5xq;wju5~Ux zhFx!qVO58s2yWTMm-TMxy$xLeX>IX}3;zku+P2doaMP}*w9~2=Isw=yZsSnhK}U1o zMnhG3AG}|t3rJw;Exr)T;M@Z6s|TpLX+nwhKC%Zx@F|LV?CV)Ot79`F_#d0t@BO@4 zcc3rMTt-3(HSOD&LveX?A3Zv zflPBSLZB%*4K-2D1as>hlvrq4YK?0&tPwk)4jDk;fP$c~Y^@b0q`)f@)ojb0 zyL8TM^;9RZ?5Xt%s&%6JeP54o zy+|e75`nnqV(kf-;27psYHeehi?-!v(Mh(%jirU7Ct1`3w-|P_Si1t~G)1Y%E?T6j NW>o{klt@%!(={Eb!WRGl literal 0 HcmV?d00001 diff --git a/src/lib/arch/zx81sd/stdlib/input.bas b/src/lib/arch/zx81sd/stdlib/input.bas index ec8bd5c71..13320a11c 100644 --- a/src/lib/arch/zx81sd/stdlib/input.bas +++ b/src/lib/arch/zx81sd/stdlib/input.bas @@ -67,11 +67,30 @@ END FUNCTION #require "io/keyboard/keyscan.asm" #require "beep.asm" +' ------------------------------------------------------------------ +' Function 'PRIVATE' to this module. +' Dado el codigo ASCII de una tecla normal (sin modificadores), da el +' simbolo del ZX81 que le corresponde bajo el antiguo SHIFT del ZX81 +' (o 0 si no tiene). Ver runtime/io/keyboard/keyscan.asm: SHIFT se ha +' redefinido para dar mayuscula, asi que esos simbolos ya no se pueden +' teclear pulsando dos teclas a la vez (no da tiempo entre pulsaciones +' humanas, sobre todo leyendo desde INPUT); en su lugar, aqui se pulsan +' de manera secuencial: primero "." y luego la tecla del simbolo. +' ------------------------------------------------------------------ +FUNCTION FASTCALL PRIVATEInputSymbolFor(ch AS UByte) AS UByte + ASM + push namespace core + call __ZX81SD_SYMBOL_FOR + pop namespace + END ASM +END FUNCTION + FUNCTION input(MaxLen AS UINTEGER) AS STRING DIM result$ AS STRING DIM i as UINTEGER DIM LastK as UByte + DIM sym as UByte result$ = "" @@ -83,6 +102,47 @@ FUNCTION input(MaxLen AS UINTEGER) AS STRING PRIVATEInputHideCursor() + IF LastK = CODE(".") THEN + REM Posible simbolo compuesto: "." seguido de otra tecla, + REM pulsadas una detras de otra (no a la vez). Se lee la + REM siguiente tecla y se comprueba si tiene simbolo. + PRIVATEInputShowCursor() + LastK = PRIVATEInputWaitKey() + PRIVATEInputHideCursor() + + IF LastK = CODE(".") THEN + REM Punto dos veces seguidas: confirma el primer punto + REM como literal y descarta el combo (la coma ya sale + REM directamente con SHIFT+"."). La segunda pulsacion + REM se consume sin mas: no abre un nuevo combo ni se + REM imprime por si misma, asi la tecla que venga a + REM continuacion se lee como una pulsacion nueva, sin + REM combinar - permite escribir un punto delante de una + REM letra que de otro modo formaria simbolo con ella. + IF LEN(result$) < MaxLen THEN + LET result$ = result$ + "." + PRINT "."; + END IF + LET LastK = 0 + ELSE + REM sym=12 (RUBOUT, tecla "0") se excluye a proposito: ya + REM se alcanza comodamente con SHIFT+0, y aceptarlo aqui + REM haria que escribir un numero decimal terminado en + REM ".0" (muy habitual) borrase el caracter anterior en + REM vez de escribir el "0". + sym = PRIVATEInputSymbolFor(LastK) + IF sym <> 0 AND sym <> 12 THEN + LET LastK = sym + ELSEIF LEN(result$) < MaxLen THEN + LET result$ = result$ + "." + PRINT "."; + END IF + REM Si no habia simbolo valido, LastK sigue siendo la + REM segunda tecla real y se procesa a continuacion con + REM normalidad: DEL, ENTER, o un caracter mas. + END IF + END IF + IF LastK = 12 THEN IF LEN(result$) THEN REM "Del" key code is 12 IF LEN(result$) = 1 THEN diff --git a/src/lib/arch/zx81sd/stdlib/mcu.bas b/src/lib/arch/zx81sd/stdlib/mcu.bas index 2c5d41408..1a7df9966 100644 --- a/src/lib/arch/zx81sd/stdlib/mcu.bas +++ b/src/lib/arch/zx81sd/stdlib/mcu.bas @@ -287,7 +287,7 @@ end function ' Recibe un stream y lo imprime linea a linea (para listados largos: ' DIR, TYPE, FREE_TXT). Devuelve el status. -function _McuStreamPrint() as ubyte +function _McuStreamPrint(newLine as ubyte) as ubyte dim c as ubyte dim line as string line = "" @@ -298,14 +298,21 @@ function _McuStreamPrint() as ubyte exit do end if if c = $76 then - print line + ' print + print line; + if len(line) <> 32 then print line = "" else line = line + chr(_McuFromZx(c)) + 'print c; " "; end if loop if len(line) > 0 then - print line + if newLine=1 then + print line + else + print line; + end if end if return McuRecv() end function @@ -424,20 +431,20 @@ end function function McuTypePrint(fname as string) as ubyte McuSend(11) _McuSendPascal(McuZxStr(fname)) - return _McuStreamPrint() + return _McuStreamPrint(1) end function ' Cmd 12: imprime el listado de un directorio (admite comodines). function McuDirPrint(mask as string) as ubyte McuSend(12) _McuSendPascal(McuZxStr(mask)) - return _McuStreamPrint() + return _McuStreamPrint(0) end function ' Cmd 14: imprime el espacio total/libre de la SD como texto. function McuFreeTxtPrint() as ubyte McuSend(14) - return _McuStreamPrint() + return _McuStreamPrint(1) end function ' Cmd 15: espacio total y libre en KB -> McuFreeTotalKb / McuFreeFreeKb. diff --git a/src/lib/arch/zx81sd/stdlib/scroll.bas b/src/lib/arch/zx81sd/stdlib/scroll.bas new file mode 100644 index 000000000..303349adf --- /dev/null +++ b/src/lib/arch/zx81sd/stdlib/scroll.bas @@ -0,0 +1,1043 @@ +' ---------------------------------------------------------------- +' This file is released under the MIT License +' +' Copyleft (k) 2008, 2025 +' by Jose Rodriguez-Rosa (a.k.a. Boriel) +' and Conrado Badenas +' +' Version zx81sd: identica a la de zx48k/stdlib/scroll.bas salvo por el +' calculo de direccion de pixel. El original llama directamente a +' PIXEL-ADD ($22ACh), rutina de la ROM del Spectrum; en zx81sd no hay ROM +' mapeada en tiempo de ejecucion, asi que esa direccion cae en mitad de +' nuestro propio codigo compilado (causaba HALT via un salto salvaje). +' Se sustituye por runtime/pixel_addr.asm, nuestra propia implementacion +' con el MISMO contrato de registros que la rutina de ROM (documentado +' en el propio fichero): entrada A=191,B=Y,C=X: salida HL=offset, +' A=X AND 7; destruye B, preserva D y E — por eso encaja sin mas cambios +' en el codigo de mas abajo, que fue escrito para la ROM del Spectrum. +' SP.PixelDown/SP.PixelUp no se tocan: son aritmetica pura sobre +' SCREEN_ADDR, sin llamadas a ROM, y ya funcionan igual en zx81sd. +' ---------------------------------------------------------------- + +#ifndef __LIBRARY_SCROLL__ + +REM Avoid recursive / multiple inclusion + +#define __LIBRARY_SCROLL__ + +#pragma push(case_insensitive) +#pragma case_insensitive = True + +' ---------------------------------------------------------------- +' sub ScrollRight +' pixel by pixel right scroll +' scrolls 1 pixel right the window defined by (x1, y1, x2, y2) +' ---------------------------------------------------------------- +sub fastcall ScrollRight(x1 as uByte, y1 as uByte, x2 as Ubyte, y2 as Ubyte) + asm + push namespace core + + PROC + LOCAL LOOP1, LOOP2, MASK1, MASK2, COLUMNN, NEQ1 + LOCAL AND1, AND2, AND3, AND4, AND5, AND6, AND7, AND8 + +; Read parameters, return if they are bad + ; a = x1 + pop hl ; RET address + pop bc ; b = y1 + pop de ; d = x2 + ex (sp), hl ; h = y2, (sp) = RET address. Stack ok now + + ld c, a ; BC = y1x1 + ld a, d + sub c + ret c ; x1 > x2 + + ld a, h + sub b + ret c ; y1 > y2 + +; Compute height and masks + inc a + ld e, a ; e = y2 - y1 + 1 + + ld a,c ;e.g. x1 = 3, mask1 = %11100000, CPL = %00011111 + and 7 + inc a + ld b,a ;B = 1 + (x1 MOD 8) = 1,2,...,8 for x1 = 0,1,...,7 + 8*n + xor a +MASK1: + rra + scf ;inject B-1 1s from the left + djnz MASK1 + ld (AND1+1),a ;e.g. %11100000 + ld (AND5+1),a + cpl + ld (AND2+1),a ;e.g. %00011111 + ld (AND7+1),a + + ld a,d ;e.g. x2 = 5, mask2 = %11111100, CPL = %00000011 + and 7 + inc a + ld b,a ;B = 1 + (x2 MOD 8) = 1,2,...,8 for x2 = 0,1,...,7 + 8*n + xor a +MASK2: + scf ;inject B 1s from the left + rra + djnz MASK2 + ld (AND4+1),a ;e.g. %11111100 + ld (AND8+1),a + cpl + ld (AND3+1),a ;e.g. %00000011 + ld (AND6+1),a + +; Compute Ncols and Display File address, and choose branch + ld a,c ;x1 + and %11111000 + rrca + rrca + rrca + ld b,a ;col1 + ld a,d ;x2 + and %11111000 + rrca + rrca + rrca ;col2 + sub b ;A = Ncols - 1 = col2 - col1 + ex af,af' ;save Ncols-1 and ZeroFlag + + ld b, h ; BC = y2x1 + ld a, 191 + call PIXEL_ADDR + res 6, h ; Starts from 0 + ld bc, (SCREEN_ADDR) + add hl, bc ; Now current offset + + ex af,af' ;load Ncols-1 and ZeroFlag + jr z,NEQ1 + +; N > 1 (there are 2 or more columns) + dec a ;A = Ncols - 2 + ld d,a ;D = N-2 +LOOP1: + push hl +; Scroll column 1 + ld a,(hl) +AND1: + and %11100000 ;e.g. x1 = 3 + ld c,a ;get out-window part of byte + ld a,(hl) +AND2: + and %00011111 ;e.g. x1 = 3 + rra ;scroll in-window part of byte + rl b ;CF is stored as bit0 of B + or c ;put out-window part of byte + ld (hl),a + inc hl +; Scroll columns 2,3,...,N-1 + ld a,d ;N-2 + and a ;A=0 iff N=2 + jr z,COLUMNN + rr b ;CF is on stage + ld b,a +LOOP2: + rr (hl) + inc hl + djnz LOOP2 + rl b ;CF is stored as bit0 of B +; Scroll column N +COLUMNN: + ld a,(hl) +AND3: + and %00000011 ;e.g. x2 = 5 + ld c,a ;get out-window part of byte + ld a,(hl) + rr b ;CF is on stage + rra ;scroll in-window part of byte +AND4: + and %11111100 ;e.g. x2 = 5 + or c ;put out-window part of byte + ld (hl),a +; Scroll another line + pop hl + dec e + ret z + call SP.PixelDown + jp LOOP1 + +; N = 1 (there is only one column) +NEQ1: + ld b,(hl) + ld a,b +AND5: + and %11100000 ;e.g. x1 = 3 + ld c,a + ld a,b +AND6: + and %00000011 ;e.g. x2 = 5 + or c + ld c,a ;get out-window part of byte + ld a,b +AND7: + and %00011111 ;e.g. x1 = 3 + rra ;scroll in-window part of byte +AND8: + and %11111100 ;e.g. x2 = 5 + or c ;put out-window part of byte + ld (hl),a +; Scroll another line + dec e + ret z + call SP.PixelDown + jp NEQ1 + ENDP + + pop namespace + end asm +end sub + +' ---------------------------------------------------------------- +' sub ScrollLeft +' pixel by pixel left scroll +' scrolls 1 pixel left the window defined by (x1, y1, x2, y2) +' ---------------------------------------------------------------- +sub fastcall ScrollLeft(x1 as uByte, y1 as uByte, x2 as Ubyte, y2 as Ubyte) + asm + push namespace core + + PROC + LOCAL LOOP1, LOOP2, MASK1, MASK2, COLUMN1, NEQ1 + LOCAL AND1, AND2, AND3, AND4, AND5, AND6, AND7, AND8 + +; Read parameters, return if they are bad + ; a = x1 + pop hl ; RET address + pop bc ; b = y1 + pop de ; d = x2 + ex (sp), hl ; h = y2, (sp) = RET address. Stack ok now + + ld c, a ; BC = y1x1 + ld a, d + sub c + ret c ; x1 > x2 + + ld a, h + sub b + ret c ; y1 > y2 + +; Compute height and masks + inc a + ld e, a ; e = y2 - y1 + 1 + + ld a,c ;e.g. x1 = 3, mask1 = %11100000, CPL = %00011111 + and 7 + inc a + ld b,a ;B = 1 + (x1 MOD 8) = 1,2,...,8 for x1 = 0,1,...,7 + 8*n + xor a +MASK1: + rra + scf ;inject B-1 1s from the left + djnz MASK1 + ld (AND3+1),a ;e.g. %00000011 + ld (AND6+1),a + cpl + ld (AND4+1),a ;e.g. %11111100 + ld (AND8+1),a + + ld a,d ;e.g. x2 = 5, mask2 = %11111100, CPL = %00000011 + and 7 + inc a + ld b,a ;B = 1 + (x2 MOD 8) = 1,2,...,8 for x2 = 0,1,...,7 + 8*n + xor a +MASK2: + scf ;inject B 1s from the left + rra + djnz MASK2 + ld (AND2+1),a ;e.g. %00011111 + ld (AND7+1),a + cpl + ld (AND1+1),a ;e.g. %11100000 + ld (AND5+1),a + +; Compute Ncols and Display File address, and choose branch + ld a,c ;x1 + and %11111000 + rrca + rrca + rrca + ld b,a ;col1 + ld a,d ;x2 + and %11111000 + rrca + rrca + rrca ;col2 + sub b ;A = Ncols - 1 = col2 - col1 + ex af,af' ;save Ncols-1 and ZeroFlag + + ld c, d + ld b, h ; BC = y2x2 + ld a, 191 + call PIXEL_ADDR + res 6, h ; Starts from 0 + ld bc, (SCREEN_ADDR) + add hl, bc ; Now current offset + + ex af,af' ;load Ncols-1 and ZeroFlag + jr z,NEQ1 + +; N > 1 (there are 2 or more columns) + dec a ;A = Ncols - 2 + ld d,a ;D = N-2 +LOOP1: + push hl +; Scroll column N + ld a,(hl) +AND1: + and %00000011 ;e.g. x2 = 5 + ld c,a ;get out-window part of byte + ld a,(hl) +AND2: + and %11111100 ;e.g. x2 = 5 + rla ;scroll in-window part of byte + rl b ;CF is stored as bit0 of B + or c ;put out-window part of byte + ld (hl),a + dec hl +; Scroll columns N-1,...,3,2 + ld a,d ;N-2 + and a ;A=0 iff N=2 + jr z,COLUMN1 + rr b ;CF is on stage + ld b,a +LOOP2: + rl (hl) + dec hl + djnz LOOP2 + rl b ;CF is stored as bit0 of B +; Scroll column 1 +COLUMN1: + ld a,(hl) +AND3: + and %11100000 ;e.g. x1 = 3 + ld c,a ;get out-window part of byte + ld a,(hl) + rr b ;CF is on stage + rla ;scroll in-window part of byte +AND4: + and %00011111 ;e.g. x1 = 3 + or c ;put out-window part of byte + ld (hl),a +; Scroll another line + pop hl + dec e + ret z + call SP.PixelDown + jp LOOP1 + +; N = 1 (there is only one column) +NEQ1: + ld b,(hl) + ld a,b +AND5: + and %00000011 ;e.g. x2 = 5 + ld c,a + ld a,b +AND6: + and %11100000 ;e.g. x1 = 3 + or c + ld c,a ;get out-window part of byte + ld a,b +AND7: + and %11111100 ;e.g. x2 = 5 + rla ;scroll in-window part of byte +AND8: + and %00011111 ;e.g. x1 = 3 + or c ;put out-window part of byte + ld (hl),a +; Scroll another line + dec e + ret z + call SP.PixelDown + jp NEQ1 + ENDP + + pop namespace + end asm +end sub + + +' ---------------------------------------------------------------- +' sub ScrollUp +' pixel by pixel up scroll +' scrolls 1 pixel up the window defined by (x1, y1, x2, y2) +' ---------------------------------------------------------------- +sub fastcall ScrollUp(x1 as uByte, y1 as uByte, x2 as Ubyte, y2 as Ubyte) + asm + push namespace core + + PROC + LOCAL LOOP1, MASK1, MASK2, COLUMNN, NEQ1 + LOCAL AND1, AND2, AND3, AND4, AND5, AND6, AND7, AND8 + LOCAL EMPTYLINE + +; Read parameters, return if they are bad + ; a = x1 + pop hl ; RET address + pop bc ; b = y1 + pop de ; d = x2 + ex (sp), hl ; h = y2, (sp) = RET address. Stack ok now + + ld c, a ; BC = y1x1 + ld a, d + sub c + ret c ; x1 > x2 + + ld a, h + sub b + ret c ; y1 > y2 + +; Compute height and masks + push ix + inc a + ld ixL,a;ixL = y2 - y1 + 1 + + ld a,c ;e.g. x1 = 3, mask1 = %11100000, CPL = %00011111 + and 7 + inc a + ld b,a ;B = 1 + (x1 MOD 8) = 1,2,...,8 for x1 = 0,1,...,7 + 8*n + xor a +MASK1: + rra + scf ;inject B-1 1s from the left + djnz MASK1 + ld (AND1+1),a ;e.g. %11100000 + ld (AND5+1),a + cpl + ld (AND2+1),a ;e.g. %00011111 + ld (AND7+1),a + + ld a,d ;e.g. x2 = 5, mask2 = %11111100, CPL = %00000011 + and 7 + inc a + ld b,a ;B = 1 + (x2 MOD 8) = 1,2,...,8 for x2 = 0,1,...,7 + 8*n + xor a +MASK2: + scf ;inject B 1s from the left + rra + djnz MASK2 + ld (AND4+1),a ;e.g. %11111100 + ld (AND8+1),a + cpl + ld (AND3+1),a ;e.g. %00000011 + ld (AND6+1),a + +; Compute Ncols and Display File address, and choose branch + ld a,c ;x1 + and %11111000 + rrca + rrca + rrca + ld b,a ;col1 + ld a,d ;x2 + and %11111000 + rrca + rrca + rrca ;col2 + sub b ;A = Ncols - 1 = col2 - col1 + ex af,af' ;save Ncols-1 and ZeroFlag + + ld b, h ; BC = y2x1 + ld a, 191 + call PIXEL_ADDR + res 6, h ; Starts from 0 + ld bc, (SCREEN_ADDR) + add hl, bc ; Now current offset + + ex af,af' ;load Ncols-1 and ZeroFlag + jr z,NEQ1 + +; N > 1 (there are 2 or more columns) + dec a ;A = Ncols - 2 + ld ixH,a ;save Ncols-2 + ld b,0 +LOOP1: + dec ixL + ld d,h + ld e,l + call z,EMPTYLINE ;HL at empty line + call nz,SP.PixelDown ;HL at line below + inc ixL ;restore iterative variable for a second check + push hl +; Scroll column 1 + ld a,(hl) +AND2: + and %00011111 ;e.g. x1 = 3 + ld c,a ;get in-window part of byte + ld a,(de) +AND1: + and %11100000 ;e.g. x1 = 3 + or c ;put in-window part of byte + ld (de),a + inc hl + inc de +; Scroll columns 2,3,...,N-1 + ld a,ixH ;load Ncols-2 + and a + jr z,COLUMNN + ld c,a + ldir +; Scroll column N +COLUMNN: + ld a,(hl) +AND4: + and %11111100 ;e.g. x2 = 5 + ld c,a ;get in-window part of byte + ld a,(de) +AND3: + and %00000011 ;e.g. x2 = 5 + or c ;put in-window part of byte + ld (de),a +; Scroll another line + pop hl + dec ixL + jp nz,LOOP1 + pop ix + ret + +; N = 1 (there is only one column) +NEQ1: + dec ixL + ld d,h + ld e,l + call z,EMPTYLINE ;HL at empty line + call nz,SP.PixelDown ;HL at line below + inc ixL ;restore iterative variable for a second check + + ld a,(hl) +AND7: + and %00011111 ;e.g. x1 = 3 +AND8: + and %11111100 ;e.g. x2 = 5 + ld c,a ;get in-window part of byte + ld a,(de) +AND5: + and %11100000 ;e.g. x1 = 3 + ld b,a + ld a,(de) +AND6: + and %00000011 ;e.g. x2 = 5 + or b + or c ;put in-window part of byte + ld (de),a +; Scroll another line + dec ixL + jp nz,NEQ1 + pop ix + ret + + defs 32,0 ;empty line with 32 zero-bytes +EMPTYLINE: + ld hl,EMPTYLINE-32 + ENDP + + pop namespace + end asm +end sub + + +' ---------------------------------------------------------------- +' sub ScrollDown +' pixel by pixel down scroll +' scrolls 1 pixel down the window defined by (x1, y1, x2, y2) +' ---------------------------------------------------------------- +sub fastcall ScrollDown(x1 as uByte, y1 as uByte, x2 as Ubyte, y2 as Ubyte) + asm + push namespace core + + PROC + LOCAL LOOP1, MASK1, MASK2, COLUMNN, NEQ1 + LOCAL AND1, AND2, AND3, AND4, AND5, AND6, AND7, AND8 + LOCAL EMPTYLINE + +; Read parameters, return if they are bad + ; a = x1 + pop hl ; RET address + pop bc ; b = y1 + pop de ; d = x2 + ex (sp), hl ; h = y2, (sp) = RET address. Stack ok now + + ld c, a ; BC = y1x1 + ld a, d + sub c + ret c ; x1 > x2 + + ld a, h + sub b + ret c ; y1 > y2 + +; Compute height and masks + push ix + inc a + ld ixL,a;ixL = y2 - y1 + 1 + ld h,b ;y1 + + ld a,c ;e.g. x1 = 3, mask1 = %11100000, CPL = %00011111 + and 7 + inc a + ld b,a ;B = 1 + (x1 MOD 8) = 1,2,...,8 for x1 = 0,1,...,7 + 8*n + xor a +MASK1: + rra + scf ;inject B-1 1s from the left + djnz MASK1 + ld (AND1+1),a ;e.g. %11100000 + ld (AND5+1),a + cpl + ld (AND2+1),a ;e.g. %00011111 + ld (AND7+1),a + + ld a,d ;e.g. x2 = 5, mask2 = %11111100, CPL = %00000011 + and 7 + inc a + ld b,a ;B = 1 + (x2 MOD 8) = 1,2,...,8 for x2 = 0,1,...,7 + 8*n + xor a +MASK2: + scf ;inject B 1s from the left + rra + djnz MASK2 + ld (AND4+1),a ;e.g. %11111100 + ld (AND8+1),a + cpl + ld (AND3+1),a ;e.g. %00000011 + ld (AND6+1),a + +; Compute Ncols and Display File address, and choose branch + ld a,c ;x1 + and %11111000 + rrca + rrca + rrca + ld b,a ;col1 + ld a,d ;x2 + and %11111000 + rrca + rrca + rrca ;col2 + sub b ;A = Ncols - 1 = col2 - col1 + ex af,af' ;save Ncols-1 and ZeroFlag + + ld b, h ; BC = y1x1 + ld a, 191 + call PIXEL_ADDR + res 6, h ; Starts from 0 + ld bc, (SCREEN_ADDR) + add hl, bc ; Now current offset + + ex af,af' ;load Ncols-1 and ZeroFlag + jr z,NEQ1 + +; N > 1 (there are 2 or more columns) + dec a ;A = Ncols - 2 + ld ixH,a ;save Ncols-2 + ld b,0 +LOOP1: + dec ixL + ld d,h + ld e,l + call z,EMPTYLINE ;HL at empty line + call nz,SP.PixelUp ;HL at line above + inc ixL ;restore iterative variable for a second check + push hl +; Scroll column 1 + ld a,(hl) +AND2: + and %00011111 ;e.g. x1 = 3 + ld c,a ;get in-window part of byte + ld a,(de) +AND1: + and %11100000 ;e.g. x1 = 3 + or c ;put in-window part of byte + ld (de),a + inc hl + inc de +; Scroll columns 2,3,...,N-1 + ld a,ixH ;load Ncols-2 + and a + jr z,COLUMNN + ld c,a + ldir +; Scroll column N +COLUMNN: + ld a,(hl) +AND4: + and %11111100 ;e.g. x2 = 5 + ld c,a ;get in-window part of byte + ld a,(de) +AND3: + and %00000011 ;e.g. x2 = 5 + or c ;put in-window part of byte + ld (de),a +; Scroll another line + pop hl + dec ixL + jp nz,LOOP1 + pop ix + ret + +; N = 1 (there is only one column) +NEQ1: + dec ixL + ld d,h + ld e,l + call z,EMPTYLINE ;HL at empty line + call nz,SP.PixelUp ;HL at line below + inc ixL ;restore iterative variable for a second check + + ld a,(hl) +AND7: + and %00011111 ;e.g. x1 = 3 +AND8: + and %11111100 ;e.g. x2 = 5 + ld c,a ;get in-window part of byte + ld a,(de) +AND5: + and %11100000 ;e.g. x1 = 3 + ld b,a + ld a,(de) +AND6: + and %00000011 ;e.g. x2 = 5 + or b + or c ;put in-window part of byte + ld (de),a +; Scroll another line + dec ixL + jp nz,NEQ1 + pop ix + ret + + defs 32,0 ;empty line with 32 zero-bytes +EMPTYLINE: + ld hl,EMPTYLINE-32 + ENDP + + pop namespace + end asm +end sub + + +' ---------------------------------------------------------------- +' sub ScrollRightAligned +' pixel by pixel right scroll. +' scrolls 1 pixel right the window defined by (x1, y1, x2, y2) +' This scroll is aligned to columns. +' x1 and x2 are divided by 8 and rounded down (floor) to get the column number (0..31) +' ---------------------------------------------------------------- +sub fastcall ScrollRightAligned(x1 as uByte, y1 as uByte, x2 as Ubyte, y2 as Ubyte) + asm + push namespace core + + PROC + LOCAL LOOP1 + LOCAL LOOP2 + + ; a = x1 + pop hl ; RET address + pop bc ; b = y1 + pop de ; d = x2 + ex (sp), hl ; h = y2, (sp) = RET address. Stack ok now + + ld c, a ; BC = y1x1 + ld a, d + sub c + ret c ; x1 > x2 + + srl a + srl a + srl a + inc a + ld e, a ; e = (x2 - x1) / 8 + 1 + + ld a, h + sub b + ret c ; y1 > y2 + + inc a + ld d, a ; d = y2 - y1 + 1 + + ld b, h ; BC = y2x1 + ld a, 191 + call PIXEL_ADDR + res 6, h ; Starts from 0 + ld bc, (SCREEN_ADDR) + add hl, bc ; Now current offset + +LOOP1: + push hl + ld b, e ; C cols + or a ; clear carry flag +LOOP2: + rr (hl) + inc hl + djnz LOOP2 + pop hl + + dec d + ret z + call SP.PixelDown + jp LOOP1 + ENDP + + pop namespace + end asm +end sub + + +' ---------------------------------------------------------------- +' sub ScrolLeftAligned +' pixel by pixel left scroll +' scrolls 1 pixel left the window defined by (x1, y1, x2, y2) +' This scroll is aligned to columns. +' x1 and x2 are divided by 8 and rounded down (floor) to get the column number (0..31) +' ---------------------------------------------------------------- +sub fastcall ScrollLeftAligned(x1 as uByte, y1 as uByte, x2 as Ubyte, y2 as Ubyte) + asm + push namespace core + + PROC + LOCAL LOOP1 + LOCAL LOOP2 + + ; a = x1 + pop hl ; RET address + pop bc ; b = y1 + pop de ; d = x2 + ex (sp), hl ; h = y2, (sp) = RET address. Stack ok now + + ld c, a ; BC = y1x1 + ld a, d + sub c + ret c ; x1 > x2 + + srl a + srl a + srl a + inc a + ld e, a ; e = (x2 - x1) / 8 + 1 + + ld a, h + sub b + ret c ; y1 > y2 + + ld c, d + inc a + ld d, a ; d = y2 - y1 + 1 + + ld b, h ; BC = y2x1 + ld a, 191 + call PIXEL_ADDR + res 6, h ; Starts from 0 + ld bc, (SCREEN_ADDR) + add hl, bc ; Now current offset + +LOOP1: + push hl + ld b, e ; C cols + or a ; clear carry flag +LOOP2: + rl (hl) + dec hl + djnz LOOP2 + pop hl + + dec d + ret z + call SP.PixelDown + jp LOOP1 + ENDP + + pop namespace + end asm +end sub + + +' ---------------------------------------------------------------- +' sub ScrolUpAligned +' pixel by pixel up scroll +' scrolls 1 pixel up the window defined by (x1, y1, x2, y2) +' This scroll is aligned to columns. +' x1 and x2 are divided by 8 and rounded down (floor) to get the column number (0..31) +' ---------------------------------------------------------------- +sub fastcall ScrollUpAligned(x1 as uByte, y1 as uByte, x2 as Ubyte, y2 as Ubyte) + asm + push namespace core + + PROC + LOCAL LOOP1 + + ; a = x1 + pop hl ; RET address + pop bc ; b = y1 + pop de ; d = x2 + ex (sp), hl ; h = y2, (sp) = RET address. Stack ok now + + ld c, a ; BC = y1x1 + ld a, d + sub c + ret c ; x1 > x2 + + srl a + srl a + srl a + inc a + ld e, a ; e = (x2 - x1) / 8 + 1 + ex af, af' ; save it for later + + ld a, h + sub b + ret c ; y1 > y2 + + inc a + ld d, a ; d = y2 - y1 + 1 + + ld b, h ; BC = y2x1 + ld a, 191 + call PIXEL_ADDR + res 6, h ; Starts from 0 + ld bc, (SCREEN_ADDR) + add hl, bc ; Now current offset + + ld a, d ; Num. of scan lines + ld b, 0 + exx + ld b, a ; Scan lines counter + ex af, af' ; Recovers cols + ld c, a + jp LOOP_START + +LOOP1: + exx + ld d, h + ld e, l + ld c, a ; C cols + call SP.PixelDown + push hl + ldir + pop hl + exx + ld a, c ; Recovers C Cols + LOCAL LOOP_START +LOOP_START: + djnz LOOP1 + + ; Clears bottom line + exx + ld (hl), 0 + ld d, h + ld e, l + inc de + ld c, a + dec c + ret z + ldir + ENDP + + pop namespace + end asm +end sub + + +' ---------------------------------------------------------------- +' sub ScrolDownAligned +' pixel by pixel down scroll +' scrolls 1 pixel down the window defined by (x1, y1, x2, y2) +' This scroll is aligned to columns. +' x1 and x2 are divided by 8 and rounded down (floor) to get the column number (0..31) +' ---------------------------------------------------------------- +sub fastcall ScrollDownAligned(x1 as uByte, y1 as uByte, x2 as Ubyte, y2 as Ubyte) + asm + push namespace core + + PROC + LOCAL LOOP1 + + ; a = x1 + pop hl ; RET address + pop bc ; b = y1 + pop de ; d = x2 + ex (sp), hl ; h = y2, (sp) = RET address. Stack ok now + + ld c, a ; BC = y1x1 + ld a, d + sub c + ret c ; x1 > x2 + + srl a + srl a + srl a + inc a + ld e, a ; e = (x2 - x1) / 8 + 1 + ex af, af' ; save it for later + + ld a, h + sub b + ret c ; y1 > y2 + + inc a + ld d, a ; d = y2 - y1 + 1 + + ld a, 191 + call PIXEL_ADDR + res 6, h ; Starts from 0 + ld bc, (SCREEN_ADDR) + add hl, bc ; Now current offset + + ld a, d ; Num. of scan lines + ld b, 0 + exx + ld b, a ; Scan lines counter + ex af, af' ; Recovers cols + ld c, a + jp LOOP_START + +LOOP1: + exx + ld d, h + ld e, l + ld c, a ; C cols + call SP.PixelUp + push hl + ldir + pop hl + exx + ld a, c ; Recovers C Cols + LOCAL LOOP_START +LOOP_START: + djnz LOOP1 + + ; Clears top line + exx + ld (hl), 0 + ld d, h + ld e, l + inc de + ld c, a + dec c + ret z + ldir + + ENDP + + pop namespace + end asm +end sub + + +#pragma pop(case_insensitive) + +REM the following is required, because it defines SCREEN_ADDR and SCREEN_ATTR_ADDR +#require "sysvars.asm" +#require "SP/PixelDown.asm" +#require "SP/PixelUp.asm" +#require "pixel_addr.asm" + +#endif From df96a23d5c04bd276b276bb2ac655ffc4a6b4449 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 09:27:27 +0200 Subject: [PATCH 11/27] zx81sd: documentacion del port y ejemplos adaptados MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Añade docs/zx81sd/ (README, USO, PRECAUCIONES, CAMBIOS_BASIC, MAP) con la guia de uso de la arquitectura zx81sd, las precauciones a tener en cuenta al escribir/portar software (sysvars, contratos de registros, teclado del ZX81, ausencia de interrupciones, namespaces ASM, eliminacion de codigo muerto, metodologia de simulacion) y el catalogo de cambios de fuente ya necesarios en ejemplos oficiales. Añade a examples/sd81/ los ejemplos adaptados/probados hasta ahora: snake_sd81.bas, flights_sd81.bas, maskedsprites_sd81.bas (MSFS sobre el mapeador de memoria, aun en proceso), pong.bas y block7test.bas. Co-Authored-By: Claude Sonnet 5 --- docs/zx81sd/CAMBIOS_BASIC.md | 304 ++++++++++ docs/zx81sd/MAP.md | 529 +++++++++++++++++ docs/zx81sd/PRECAUCIONES.md | 184 ++++++ docs/zx81sd/README.md | 79 +++ docs/zx81sd/USO.md | 88 +++ examples/sd81/block7test.bas | 63 +++ examples/sd81/flights_sd81.bas | 129 +++++ examples/sd81/maskedsprites_sd81.bas | 810 +++++++++++++++++++++++++++ examples/sd81/pong.bas | 237 ++++++++ examples/sd81/snake_sd81.bas | 165 ++++++ 10 files changed, 2588 insertions(+) create mode 100644 docs/zx81sd/CAMBIOS_BASIC.md create mode 100644 docs/zx81sd/MAP.md create mode 100644 docs/zx81sd/PRECAUCIONES.md create mode 100644 docs/zx81sd/README.md create mode 100644 docs/zx81sd/USO.md create mode 100644 examples/sd81/block7test.bas create mode 100644 examples/sd81/flights_sd81.bas create mode 100644 examples/sd81/maskedsprites_sd81.bas create mode 100644 examples/sd81/pong.bas create mode 100644 examples/sd81/snake_sd81.bas diff --git a/docs/zx81sd/CAMBIOS_BASIC.md b/docs/zx81sd/CAMBIOS_BASIC.md new file mode 100644 index 000000000..5228bfff4 --- /dev/null +++ b/docs/zx81sd/CAMBIOS_BASIC.md @@ -0,0 +1,304 @@ +# Cambios en fuentes BASIC para portar ejemplos oficiales a zx81sd + +Este fichero recopila, para cada ejemplo oficial de `examples/` que se ha +probado en zx81sd, si hizo falta tocar el fuente BASIC (nunca el original: +siempre una copia en `examples/sd81/`) o solo cambiar las flags de +compilación. + +Regla de fondo: el fuente oficial de zxbasic **nunca se modifica**. Cuando +un programa depende de una sysvar o dirección absoluta específica del +Spectrum, se hace una copia adaptada junto al original. + +--- + +## 1. `examples/english/comecoquitos.bas` — SIN cambios de fuente + +Compila tal cual. Solo necesita flags de línea de comandos: + +``` +python -m src.zxbc.zxbc comecoquitos.bas --arch zx81sd --string-base 1 --array-base 1 -o comecocos.bin +``` + +**Por qué**: el fuente usa indexación de strings 1-based (estilo Sinclair +BASIC clásico, `l$(f)`, slicing). Con la base 0 por defecto de zxbasic, +las comparaciones de colisión salen desplazadas una posición — no es un +bug del port, en zx48k pasa exactamente igual sin esas flags. + +--- + +## 2. `examples/english/snake_en.bas` → `examples/sd81/snake_sd81.bas` + +Un solo cambio, una línea: + +```diff +-73 POKE UINTEGER 23675, @udg(0, 0): REM Sets UDG variable to first element ++73 POKE UINTEGER $8002, @udg(0, 0): REM sysvar UDG de zx81sd (en Spectrum era 23675) +``` + +**Por qué**: `23675` ($5C7B) es la dirección absoluta del sysvar `UDG` en +el mapa de memoria del **Spectrum**. En zx81sd el mismo sysvar vive en +`$8002` (ver `SYSVAR_BASE+2` en `src/lib/arch/zx81sd/runtime/sysvars.asm`). +Sin el cambio, el POKE cae en RAM libre y los UDGs (cabeza y fruta de la +serpiente) nunca se activarían — el juego funcionaría pero pintaría +espacios en vez de los gráficos. + +**Patrón general**: cualquier ejemplo que haga `POKE`/`PEEK` a una +dirección de sysvar del Spectrum en vez de usar la función de más alto +nivel (aquí habría bastado con no usar UDG a pelo) necesita este tipo de +traducción de dirección. Ver también flights.bas más abajo. + +--- + +## 3. `examples/flights.bas` → `examples/sd81/flights_sd81.bas` + +Tres cambios de contenido (más limpieza cosmética de espacios finales sin +efecto funcional, no listada): + +### 3.1 Eliminar un POKE de sysvar del Spectrum sin equivalente + +```diff +-1 POKE 23658,8: BORDER 1: PAPER 1: INK 7: CLS ++1 BORDER 1: PAPER 1: INK 7: CLS +``` + +**Por qué**: `23658` ($5C6A) es `REPDEL` (retardo de repetición de tecla) +en el Spectrum — no existe ese sysvar en zx81sd, y el POKE escribiría +sobre una dirección de RAM arbitraria. Se elimina sin más: el efecto +(ajustar el auto-repeat del teclado) no tiene equivalente ni falta le +hace al juego. + +### 3.2 Traducir el sysvar COORDS a su dirección real en zx81sd + +```diff +-2295 IF gc<>0 THEN PLOT OVER 1;x1,168+16-y1: DRAW OVER 1;x2-PEEK 23677,168+16-y2-PEEK 23678: END IF ++2295 IF gc<>0 THEN PLOT OVER 1;x1,168+16-y1: DRAW OVER 1;x2-PEEK $8004,168+16-y2-PEEK $8005: END IF +``` +(y tres apariciones más idénticas en las líneas 2370, 2378 y 2445) + +**Por qué**: `23677`/`23678` ($5C7D/$5C7E) son el sysvar `COORDS` +(última coordenada de `PLOT`) del Spectrum. En zx81sd vive en `$8004`/ +`$8005` (`COORDS EQU SYSVAR_BASE+$04` en `sysvars.asm`). Sin la +traducción, los `PEEK` leen RAM del programa compilado en vez de la +coordenada real, y el `DRAW` de la línea del horizonte sale con un +desplazamiento aleatorio dependiente del contenido de esa RAM — este fue +el primer síntoma reportado ("pinta mal la línea del horizonte"). + +### 3.3 Comparaciones de tecla a minúsculas + +```diff +-3010 IF k$="S" THEN LET pow=pow-1: END IF +-3020 IF k$="F" THEN LET pow=pow+1: END IF +-3030 IF k$="Q" THEN LET pt=pt+1: END IF +-3040 IF k$="A" THEN LET pt=pt-1: END IF +-3050 IF k$="O" AND rl>-30 THEN LET rl=rl-1: END IF +-3060 IF k$="P" AND rl<30 THEN LET rl=rl+1: END IF ++3010 IF k$="s" THEN LET pow=pow-1: END IF ++3020 IF k$="f" THEN LET pow=pow+1: END IF ++3030 IF k$="q" THEN LET pt=pt+1: END IF ++3040 IF k$="a" THEN LET pt=pt-1: END IF ++3050 IF k$="o" AND rl>-30 THEN LET rl=rl-1: END IF ++3060 IF k$="p" AND rl<30 THEN LET rl=rl+1: END IF +``` +(y las comparaciones de "Y"/"N" en las líneas 6150/6160) + +**Por qué (este cambio corresponde al esquema de teclado antiguo — ver +nota de "ya no es necesario" más abajo)**: en el momento de portar +`flights.bas`, `zx81sd/runtime/io/keyboard/keyscan.asm` (reescaneo a +mano del **teclado físico del ZX81**, 40 teclas, ya que el SD81 Booster +no tiene teclado Spectrum) solo devolvía minúscula siempre, sin importar +si se pulsaba `SHIFT` o no — `SHIFT+letra` no producía la mayúscula de +esa letra, sino uno de los símbolos clásicos del ZX81 (dos puntos, +comillas, `+`, `-`...). Con ese esquema no existía combinación física +que reprodujera "CAPS SHIFT+S" al estilo Spectrum, y la única +adaptación posible era comparar contra minúscula sin shift. Mismo +motivo, ya resuelto antes por otra vía en `comecoquitos.bas` (ahí el +propio fuente ya comparaba en minúscula, "o","p","q","a","y","n" — +coincidencia de que su autor tecleó así, no algo que tuviéramos que +tocar) y en `snake_sd81.bas` (compara "O"/"o" con `OR`, cubriendo ambos +casos sin necesidad de tocarlo). + +**Ya no es necesario (2026-07-04)**: `keyscan.asm` se rediseñó para que +la pulsación directa de una letra dé minúscula y `SHIFT+letra` dé la +MAYÚSCULA de esa letra (además de `SHIFT+"2"` como CAPS LOCK persistente +y los símbolos clásicos del ZX81 alcanzables con `"."+tecla` desde +`INPUT()`). Con el esquema actual, `INKEY$="S"` ya funcionaría de forma +natural pulsando `SHIFT+S`, exactamente como en un Spectrum — el cambio +de caso de `flights_sd81.bas` documentado arriba fue necesario en su +momento pero ya no lo sería si se portara hoy desde cero. Se deja +constancia aquí en vez de revertir la copia ya probada. Detalle completo +del rediseño en [MAP.md](MAP.md), sección "Esquema de teclado nuevo". + +--- + +## 4. `examples/english/4inarow.bas` — SIN cambios de fuente + +Compila con flags por defecto: + +``` +python -m src.zxbc.zxbc 4inarow.bas --arch zx81sd -o row4.bin +``` + +Sus arrays usan índices 1..8 que caben en base 0 por defecto sin +colisión de slicing (no hace slicing de strings), y sus comparaciones de +tecla ya usan minúsculas ("y"/"n" en los prompts). Ejercita el código de +arcos (`DRAW 8,0,PI`) y `CIRCLE` sin necesitar ningún parche. + +--- + +## 5. `examples/scroll.bas` — SIN cambios de fuente + +Compila con flags por defecto: + +``` +python -m src.zxbc.zxbc scroll.bas --arch zx81sd -o scroll.bin +``` + +El problema no estaba en el ejemplo sino en la librería: `scroll.bas` no +tenía versión zx81sd — se usaba tal cual la de zx48k, cuyas 8 subs +(`ScrollRight/Left/Up/Down` + variantes `*Aligned`) llaman a `$22AC` +(rutina *PIXEL-ADD* de la ROM del Spectrum, inexistente en zx81sd). Se +creó `src/lib/arch/zx81sd/stdlib/scroll.bas`, idéntica salvo sustituir +esas 8 llamadas por `call PIXEL_ADDR` (nuestra propia implementación, +mismo contrato de registros que la ROM — ver [MAP.md](MAP.md)). El +ejemplo en sí no necesitó ningún cambio, igual que 4inarow.bas. + +--- + +## 6. `examples/maskedsprites.bas` → `examples/sd81/maskedsprites_sd81.bas` + +**En proceso — aún no funciona del todo bien.** + +Un cambio de fondo: `WaitForNewFrame` (definida en el propio ejemplo, no +en la librería) reescrita para no depender de interrupciones: + +```diff +- ld de,23672 +- ld c,a ; A = C = minimumNumberofFramesToWaitSinceLastWait +- READ_IFF2 +- ex af,af' +- ei ; interrupts MUST be enabled before HALT +- halt ++ ld de, FRAMES ++ ld c,a ; C = minimumNumberofFramesToWaitSinceLastWait ++ call VSYNC_TICK ; garantiza al menos un frame esperado (sustituye EI+HALT) + wait: + ld a,(de) + sub (hl) + cp c +- jr c,wait +- ld a,(de) +- ld (hl),a +- ex af,af' +- ret pe +- di +- RET ++ jr nc,enough ++ call VSYNC_TICK ++ jr wait ++enough: ++ ld a,(de) ++ ld (hl),a ++ ret +``` + +**Por qué**: `23672` es el contador `FRAMES` de la ROM del Spectrum, +incrementado automáticamente por la interrupción IM1 de 50Hz. El +original hace un `HALT` inicial (espera a la interrupción) y luego un +bucle que confía en que la interrupción lo siga incrementando en segundo +plano mientras el bucle solo *comprueba* sin volver a esperar. En +zx81sd no hay interrupciones (DI permanente; el vector `$0038` es una +trampa `DI;HALT`, no un manejador real) — ese `HALT` no despertaría +nunca. Se sustituye por `VSYNC_TICK` (sondeo del contador de pulsos +VSYNC hardware del SD81 Booster por puerto, la misma rutina que ya usa +`PAUSE`), llamada explícitamente en cada vuelta que haga falta esperar. + +**Actualización — MSFS portado al mapeador de zx81sd (bloque 7)**: el +riesgo de arriba ya no aplica. Se creó +`src/lib/arch/zx81sd/stdlib/cb/maskedsprites.bas` (override completo de +la librería compartida, que sigue intacta): `SetBankPreservingINTs`/ +`GetBankPreservingRegs` reescritas en ASM a mano sobre el puerto `$E7` +(bloque 7, `$E000-$FFFF` — reservado en nuestro mapa de memoria para +"banking de datos, mapas, sprites"), y `MaskedSpritesFileSystemStart` +fija en `$E000` en vez de "lo que quede hasta `$FFFF`" (que asumía RAM +plana Spectrum). El resto de MSFS (cientos de líneas de álgebra de +bloques) es agnóstico de banco/dirección y se copió sin tocar una línea. +Detalle completo, incluido un bug real cazado en el proceso (registros +no preservados si se escriben en BASIC en vez de ASM), en [MAP.md](MAP.md). + +Pese a los fixes de arriba (confirmados con simulación y, en parte, en +emulador/hardware real), la librería en su estado actual **sigue sin +funcionar del todo bien** — trabajo en curso, no dar por cerrado este +ejemplo todavía. + +--- + +## 7. `examples/sd81/pong.bas` — transcripción clásica, un cambio ASM + +No es un ejemplo oficial de `examples/` sino una transcripción clásica +de Pong al estilo Sinclair BASIC (GOSUB/números de línea), añadida +directamente en `examples/sd81/`. Usa comparaciones de tecla ya en +minúscula ("4","q","3","a"), coincidentes con el esquema de teclado +actual sin necesidad de tocar nada — el único cambio fue sustituir la +sincronización de pantalla: + +```diff + ASM +- call VSYNC_TICK ; garantiza al menos un frame esperado (sustituye EI+HALT) ++ call .core.VSYNC_TICK ; garantiza al menos un frame esperado (sustituye EI+HALT) + ; halt ; Avoids screen flickering + END ASM +``` + +**Por qué**: `vsync.asm` envuelve `VSYNC_TICK` en +`push namespace core ... pop namespace`; al llamarlo desde un bloque +`ASM ... END ASM` que no está dentro de ese namespace hace falta el +prefijo completo `.core.VSYNC_TICK` — mismo patrón de error que en +`maskedsprites_sd81.bas` (ver [PRECAUCIONES.md](PRECAUCIONES.md), +sección 5). Sin el prefijo, el compilador da +`Undefined GLOBAL label '.VSYNC_TICK'`. + +--- + +## 8. `examples/sd81/block7test.bas` — sin equivalente oficial, prueba de mapeador + +Programa mínimo (no transcripción de ningún ejemplo) escrito para +confirmar, de forma aislada, que el mapeador de memoria del SD81 Booster +provee almacenamiento independiente por página: escribe patrones +distintos en las páginas 20 y 63 del bloque 7 (`$E000-$FFFF`) vía +`Map()` y verifica que no se pisan entre sí. Sirvió para descartar el +mapeador como causa durante la investigación del bug de MSFS (ver +[MAP.md](MAP.md)). Se conserva como ejemplo de referencia de uso directo +de `Map()`/`MapGet` sobre el bloque 7. + +--- + +## Resumen para el manual + +| Ejemplo | Copia en zx81sd | Cambios de fuente | Flags de compilación | +|---|---|---|---| +| comecoquitos.bas | no hace falta copia si no se toca el original | ninguno | `--string-base 1 --array-base 1` | +| snake_en.bas | `snake_sd81.bas` | 1 línea (dirección UDG) | ninguna especial | +| flights.bas | `flights_sd81.bas` | 3 tipos de cambio (POKE eliminado, 4× PEEK COORDS, 8× case de tecla) | ninguna especial | +| 4inarow.bas | no hace falta copia | ninguno | ninguna especial | +| scroll.bas | no hace falta copia | ninguno (el fix fue en la librería `stdlib/scroll.bas`) | ninguna especial | +| maskedsprites.bas | `maskedsprites_sd81.bas` | `WaitForNewFrame` reescrita (EI+HALT → VSYNC_TICK); librería `cb/maskedsprites.bas` portada al mapeador (bloque 7) — **en proceso, aún no funciona del todo bien** | ninguna especial | +| pong.bas (no oficial) | `pong.bas` en `examples/sd81/` | 1 línea ASM (namespace de VSYNC_TICK) | ninguna especial | +| block7test.bas (no oficial) | `block7test.bas` en `examples/sd81/` | n/a (escrito directamente para zx81sd) | ninguna especial | + +Patrón identificado para futuros ejemplos: + +1. Buscar `POKE`/`PEEK` a literales numéricos: casi siempre es una + sysvar del Spectrum que hay que traducir a su dirección zx81sd + equivalente (ver `src/lib/arch/zx81sd/runtime/sysvars.asm`). +2. Comparaciones de `INKEY$` contra mayúsculas de teclas de control + (`"S"`, `"Q"`, etc.): con el esquema de teclado **actual** + (pulsación directa = minúscula, `SHIFT+letra` = mayúscula) esto ya + funciona igual que en un Spectrum y **no hace falta tocar nada** — + el cambio a minúscula documentado arriba para `flights.bas` + corresponde a una versión anterior de `keyscan.asm` (ver nota "Ya no + es necesario" en la sección 3.3) y se mantiene aquí solo como + registro histórico de esa copia ya generada. +3. Código ASM inline que llame a rutinas envueltas en + `push namespace core` (como `VSYNC_TICK`) necesita el prefijo + `.core.` si el bloque `ASM` que lo invoca no está ya dentro de ese + namespace (ver caso de `pong.bas` arriba). diff --git a/docs/zx81sd/MAP.md b/docs/zx81sd/MAP.md new file mode 100644 index 000000000..a6eec0504 --- /dev/null +++ b/docs/zx81sd/MAP.md @@ -0,0 +1,529 @@ +# Mapa de tests de depuración (sesión DRAW3 / arco) + +Nota sobre rutas: esta bitácora se escribió originalmente en el +repositorio complementario de pruebas del port (donde los `.bas` citados +vivían todos en un directorio `tests_debug/`). Los ejemplos que se +consideraron suficientemente maduros para publicarse ya están copiados +en este repositorio, en [`examples/sd81/`](../../examples/sd81/) +(`flights_sd81.bas`, `snake_sd81.bas`, `maskedsprites_sd81.bas`, +`pong.bas`, `block7test.bas` — ver [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md)). +El resto de fuentes de depuración puntual mencionadas aquí (`diag1-6`, +`t_arc*`, `trig_test`, `heaptest`, `keytest`...) siguen solo en el +repositorio complementario, no en este. + +Compilación de cada uno (desde la raíz de este repositorio): +``` +python -m src.zxbc.zxbc examples\sd81\.bas --arch zx81sd -o .bin +python split_sd81.py .bin +``` +(`split_sd81.py` vive en el repositorio complementario — ver [USO.md](USO.md).) + +| Fuente | Prefijo SD81 | Qué prueba | Resultado obtenido | +|------------------------|--------------|---------------------------------------------------------------------|---------------------| +| `str_test.bas` | STRTEST | PRINT/STR$ de FLOAT (Fase 3) | OK: 7 / 7.5 / -3.25 / 0 / 123.5 / -0.5 | +| `trig_test.bas` | TRIGTST | SQR/SIN/COS/EXP/LN/ATN con argumentos triviales (Fase 4) | OK: 3 / 1.41421 / 0 / 1 / 2.71828 / 1 / 3.14159 | +| `trig2_test.bas` | TRIG2 | SIN/COS con argumentos NO triviales (1, 1.5708, 3.14159) | OK: 0.8147(4?) / 0.5403 / 0.99999 / -0.99999 / 0 | +| `draw_arc_test.bas` | DRAWARC | DRAW+DRAW+CIRCLE combinados (primer intento de arco) | Solo aparece una línea vertical, CIRCLE nunca se ve | +| `t_line.bas` | TLINE | `DRAW 20,0` (2 args, sin ángulo, sin FP) | OK: línea horizontal | +| `t_circle.bas` | TCIRC | `CIRCLE 60,30,20` sola | OK: círculo correcto | +| `t_arc.bas` | TARC | `DRAW 20,0,3.14159` (arco solo, offset horizontal, 180°) | BUG: línea vertical (no horizontal, no arco) | +| `t_arc2.bas` | TARC2 | `DRAW 30,10,1.5708` (offsets asimétricos, 90°) | BUG: línea vertical con leve inclinación a la derecha | +| `t_arc3.bas` | TARC3 | `PLOT 100,96` + `DRAW 20,0,3.14159` (centrado, con margen) | BUG: patrón de varias líneas en estrella/caóticas desde el centro; una se sale de pantalla hacia el área de atributos | +| `diag1.bas` | DIAG1 | ASM inline: llama a CD-PRMS1 (L247D) directamente con z=40, A=pi/2, e imprime mem-1/mem-3/mem-4/mem-0/nº de líneas | Pantalla en blanco — bug del propio test (`#include` dentro de `ASM` mete código ejecutable en el flujo lineal). Sustituido por diag2 | +| `diag2.bas` | DIAG2 | Igual que diag1 pero sin includes manuales: variables BASIC + PRINT normal | OK: valores exactos esperados → CD-PRMS1 numéricamente correcto | +| `diag4.bas` | DIAG4 | Con `-D DRAW3_DEBUG`: imprime solo N (nº de segmentos capturados en el hook de draw3.asm) | OK: 16 → el hook de captura funciona | +| `diag5.bas` | DIAG5 | diag4 + bucle de recorrido del buffer (2 páginas SD81) | No imprimía; descartado, sustituido por diag6 (1 página) | +| `diag6.bas` | DIAG6/DIAG7 | diag4 + bucle, 1 página. Su volcado de $152D + breakpoints de escritura en $8004 localizaron el bug | OK: 16/16. DIAG7 = idéntico recompilado tras el fix | +| `arcfix.bas` | ARCFIX | Test de referencia idéntico al Spectrum real: `PLOT 100,100` + 2 DRAW arco + CIRCLE | **OK tras el fix**: gancho + círculo, igual que la foto del Spectrum real | + +## Bug de DRAW3 (arco) — RESUELTO + +**Causa raíz** (nada que ver con el calculador FP, cuya matemática resultó +ser exacta): `src/lib/arch/zx81sd/runtime/pixel_addr.asm` destruía el +registro **D** (lo usaba como scratch para V=191−Y). Pero `draw.asm` +(heredado de zx48k) salva la coordenada Y del Bresenham en D' alrededor de +la llamada (`ld d,b / call PIXEL_ADDR / ld b,d`), porque PIXEL-ADD ($22AC) +de la ROM Spectrum preserva DE. Resultado: cada línea con componente +vertical arrancaba internamente con Y=191−y1, corrompiendo COORDS y el +trazado. Las líneas horizontales (t_line) y CIRCLE (que no pasa por ese +camino) salían bien, lo que despistó la investigación inicial. + +**Fix**: PIXEL_ADDR reescrito para usar B como scratch (ya se destruía, +igual que en la ROM) y preservar D y E. Verificado en hardware con ARCFIX +(idéntico al resultado del Spectrum real) y DIAG7. + +**Método de localización**: hook `#ifdef DRAW3_DEBUG` en draw3.asm (activado +con `-D DRAW3_DEBUG`) que captura por cada segmento: |Dy|,|Dx|,signos y +COORDS previas en un buffer (`DRAW3_DEBUG_BUF`); volcado de memoria del +buffer en el debugger de EightyOne + breakpoints de escritura en COORDS +($8004/$8005). El volcado demostró que la posición FP acumulada era +perfecta y que COORDS quedaba mal tras cada línea → el bug estaba en +__DRAW/PIXEL_ADDR, no en fp_calc.asm ni draw3.asm. + +## Ficheros fuente modificados/creados en esta sesión (motor FP + arco) + +- `src/lib/arch/zx81sd/runtime/fp_calc.asm` — Fases 1-5 (motor CALCULATE, + trig/log/exp/sqrt, y ahora STK-TO-A/STK-TO-BC/CD-PRMS1 para el arco). + También se le añadió `#include once ` (bug independiente: + se incluye siempre en todo binario zx81sd, así que debía bastarse a + sí mismo). +- `src/lib/arch/zx81sd/runtime/draw3.asm` — NUEVO. Override de + `zx48k/runtime/draw3.asm` que sustituye las llamadas a direcciones + ROM fijas por las rutinas portadas en `fp_calc.asm`. Incluye + instrumentación de traza tras `#ifdef DRAW3_DEBUG` (inactiva por + defecto; se activa compilando con `-D DRAW3_DEBUG`). +- `src/lib/arch/zx81sd/runtime/pixel_addr.asm` — FIX del bug del arco: + ahora preserva D y E (antes destruía D, rompiendo el Bresenham de + draw.asm en toda línea no horizontal). +- `src/lib/arch/zx81sd/runtime/fp_tostr.asm`, `printf.asm`, `str.asm` — + de la Fase 3 (PRINT/STR$ de FLOAT), sin cambios en esta sesión. + +## Sonido: BEEP y PLAY (chips AY ZonX del SD81) + +| Fuente | Prefijo SD81 | Qué prueba | Resultado | +|-----------------|--------------|----------------------------------------------------------|-----------| +| `beeptest.bas` | BEEPTS2 | BEEP variable (runtime FP) + BEEP constante (corrección de reloj 13/14 en __BEEPER) | OK: escala + DO/DO' + LA 440 | +| `playtest.bas` | PLAYTS2 | PLAY 3 canales + comparación AY/beeper | OK (¡ojo: notas en minúscula = octava abajo!) | +| `aycal.bas` | AYCAL | Emparejamiento AY vs beeper por semitonos | Sirvió para detectar el desfase | +| `aycal2.bas` | AYCAL2 | Duraciones cronometrables + pareja directa | FFT: beeper 434.5 (correcto, pacing emulador), AY 220 | +| `aycal3.bas` | AYCAL3 | Igual con notas en MAYÚSCULA | OK: unísono 440/440 | + +Lección de la investigación de la "octava fantasma": en el MML de PLAY +(semántica del BASIC 128K), las notas en MINÚSCULA suenan una octava por +debajo de la octava actual. Los tests iniciales usaban minúsculas y el AY +sonaba a 220 Hz *por diseño*. El emulador, la tabla de divisores +(1.625 MHz) y el beeper (3.25 MHz) eran correctos. Se verificó con FFT +sobre la salida de audio grabada. De regalo se corrigió un bug latente +real de EightyOne (el reloj del AY quedaba con el de la tarjeta del +diálogo de hardware en vez del ZonX forzado por el SD81: faltaba llamar a +Sound.InitDevices() tras forzar machine.aytype). + +## Librería MCU (SD81 Booster) — `zx81sd/stdlib/mcu.bas` + `joy.bas` + +| Fuente | Prefijo SD81 | Qué prueba | Resultado | +|----------------|--------------|------------------------------------------------------------|-----------| +| `joytest.bas` | JOYTS2 | `Joy("QAOPM")` (cmd 21) + validación local + eco INKEY$ | OK | +| `mcutest.bas` | MCUTST | VERSION, GET/SETBYTE, PWD, SAVE+LOAD+verificación, DEL, FREE, RTC, BAT, DIR, AY2 por registros, AyPlay | pendiente | +| `maptest.bas` | MAPTST | `Map(bloque,pagina)`/`MapGet` — mapeador $E7: firmas en 2 páginas conmutando el bloque 5 y verificación | OK | +| `exttest.bas` | EXTTST | Extensiones no-MCU: `HexPoke` (*HEX), `MemMove` (*LDIR/*LDDR, stdlib), `StrInv`/`StrBold` (*INV/*BOLD) | OK | +| `ftest.bas` | FTEST | Handles F_*: SAVE, F_OPEN_ZX81 (cmd 58), F_SEEK, F_READ con verificación, F_WRITE+relectura, F_CLOSE, DEL | OK | +| `lstest.bas` | LSTEST | Statements LOAD/SAVE/VERIFY ... CODE nativos → SD (override runtime load.asm/save.asm, cmd 9/10): SAVE+LOAD+verificación, VERIFY ok/corrupto (ERR 26), fichero inexistente (ERR 26) | OK | + +Arquitectura: `mcu.bas` contiene las primitivas del protocolo en ASM +(McuSend/McuRecv/McuSendBlock/McuRecvBlock — las *Block son el camino +crítico de LOAD/SAVE/F_READ/F_WRITE) y los wrappers de todos los +comandos del manual (sistema, ficheros, handles F_*, hardware, voz, +AY2/VGM/PEG, RTC/BAT). Conversión ASCII↔ZX81 automática en los comandos +de texto. `joy.bas` es una capa fina sobre `mcu.bas`. + +Notas de protocolo (extraídas de SD81Booster.cpp del emulador): +- Tras CADA operación en $A7 se espera el cambio del bit 7 de $AF. + NUNCA escribir en $AF (reset del MCU). +- Strings Z80→MCU: byte de longitud + datos (el MCU convierte ZX81→ASCII + salvo comandos "raw": JOY, BINARY_SAY, F_OPEN). +- Streams MCU→Z80 (PWD/DIR/TYPE/FREE_TXT): pedir cada carácter + escribiendo CMD_NEXTCH ($0D); fin = EOT ($6F); después llega el status. +- Respuestas de longitud fija (LOAD, FREE, RTC, BAT, F_READ): ráfaga + de bytes leyendo $A7 con espera de reloj entre cada uno. +- F_OPEN: confirmado en el firmware (COMMANDS.cpp) que el MCU asigna el + handle y lo devuelve (el manual estaba mal y se ha corregido). Añadido + F_OPEN_ZX81 (58) a la librería y al emulador. +- OPENDIR/GETROWLEN/GETROW (16-18) no están emulados en EightyOne: + probarlos solo en hardware real. + +## Test de integración: comecoquitos.bas (ejemplo oficial de zxbasic) + +| Fuente | Prefijo SD81 | Qué prueba | Resultado | +|---|---|---|---| +| `examples/english/comecoquitos.bas` | COMECO | Juego completo de 1985: FP, strings/slices, arrays, UDGs, bloques gráficos, color/FLASH/BRIGHT, INKEY$, BEEP, RND | OK — idéntico al .tap de Spectrum | +| `fpleak.bas` | FPLEAK | Detector de fugas de la pila FP por bloques (lee $8024 tras cada idiom) | Sirvió para acotar; la "fuga" era corrupción por UDGs | +| `blocktest.bas` | BLKTST | Los 16 gráficos de bloque CHR$(128)-143 | OK tras el fix de PO_GR_1 | + +Tres bugs del runtime cazados con este juego (commits 9903c866 y 9a24059e): +1. UDG apuntaba 128 bytes más allá del final de la fuente (96 chars, no + 256): los POKE USR CHR$ machacaban código del runtime → cuelgue en el + primer uso del calculador. Fix: área dedicada de 21 UDGs. +2. INKEY$ devolvía mayúsculas; el modo L del Spectrum (y los programas de + la época) usan minúsculas. Fix: tabla de keyscan en minúscula. +3. PO_GR_1 (bloques CHR$(128)-143) generaba patrones corruptos (OR al + registro equivocado + cuadrantes izq/der invertidos). Fix: algoritmo + literal de la ROM. + +RECETA para ejemplos clásicos transcritos de Sinclair BASIC: compilar con +`--string-base 1 --array-base 1` (indexación 1-based). Sin ello, las +colisiones por slicing de strings salen desplazadas una posición (no es +un bug del port: en zx48k pasa igual). + +## Heap en $8100 + traps de cinta de EightyOne — RESUELTO (2026-07-04) + +| Fuente | Prefijo SD81 | Qué prueba | Resultado | +|---|---|---|---| +| `tests_debug/heaptest.bas` | HEAPA..HEAPE | Gestor de memoria dinámica (3 fases: crecimiento char a char, REALLOC grandes, STR$ en bucle) con el heap en distintas direcciones | Bisección que aisló el bug | +| `tests_debug/memtest.bas` | MEMTST | R/W patrón dependiente de dirección en $8100-$BFFF (2 pasadas) | OK — descartó el hardware/paginación | +| `tests_debug/inputtest.bas` | INTEST | INPUT() mínimo aislado | Reproducía el cuelgue | +| `examples/sd81/flights_sd81.bas` | FLIGHT | Simulador de vuelo: PEEK COORDS ($8004/5), FP intensivo, INPUT | Adaptación de examples/flights.bas | + +Dos bugs encadenados, cazados el 2026-07-04: + +1. **Compilador (`src/arch/zx81sd/backend/main.py`)**: `heap_size`/`heap_address` + se registraban con `ADD_IF_NOT_DEFINED`, pero el backend Z80 genérico ya + las define antes (4768 / None) → los valores zx81sd ($8100 / 16127) nunca + se aplicaban y el heap acababa inline (DEFS) dentro de la zona ejecutable, + desperdiciando 4768 bytes y limitando el heap. Fix: asignación directa + `OPTIONS.heap_size/heap_address` (la CLI puede seguir sobreescribiendo). + +2. **Emulador (`Eightyone2/src/ZX81/rompatch.cpp`, `PatchTest`)**: los traps + de cinta de la ROM ZX81 ($0207/$02FF/$031E/$0356) se disparaban comparando + PC + `memory[pc]` **plano**, que conserva la ROM aunque el SD81 tenga RAM + mapeada. Con el heap en EQU el runtime baja $12A0 bytes y la división + __DIVU16_FAST aterrizaba en $02FF → el trap de SAVE hacía `DE=1` en mitad + de la división → cociente basura estable → bucle infinito de dígitos en + __PRINTU_LOOP (PUSH AF sin pop) → la pila descendía arrasando el runtime. + Los builds con heap inline eran inmunes de casualidad: las 4 direcciones + trampa caían dentro del bloque DEFS (datos, el PC nunca pasa por ahí). + Fix: `PatchTest` lee el byte con `zx81_PatchPeek()` (mapper-aware). + En hardware real este bug NO existe (no hay traps). + +Metodología que lo resolvió: simulación determinista del binario con la +librería Python `z80` (pip install z80) — diff de integridad de la zona de +código tras cada tramo + breakpoints comparando registros con EightyOne. +El binario era correcto en Z80 puro → la divergencia estaba en el emulador. +Arnés en el scratchpad de la sesión (runsim*.py, reproducible). + +## Scroll de PRINT saltaba a la ROM del Spectrum — RESUELTO (2026-07-04) + +Tercer bug de la cadena de flights.bas (viento=10, dir=100 → HALT): el +`print.asm` de zx81sd conservaba del zx48k el fallback +`__SCROLL_SCR EQU 0DFEh` (rutina CL-SC-ALL de la ROM del Spectrum). En +zx81sd no hay ROM: el primer PRINT que desbordaba la pantalla hacía CALL +a la línea BASIC compilada que casualmente ocupara $0DFE → ejecución +salvaje → RETURN de gosub sacando basura → HALT. Dependía de la entrada +porque el nº de dígitos tecleados movía el cursor: con viento "1"/"0" el +texto no llegaba a desbordar; con "10"/"100" sí. Ningún test anterior lo +pilló porque todos usan PRINT AT (nunca scroll). + +Fix: la implementación por búfer (`__ZXB_ENABLE_BUFFER_SCROLL`, scrollea +vía SCREEN_ADDR/SCREEN_ATTR_ADDR) es ahora la rama única de __SCROLL_SCR +en zx81sd/runtime/print.asm. Verificado con el simulador Python + teclado +scriptado: el bucle principal del juego mantiene SP estable durante miles +de pasos. Ojo futuro: revisar cualquier otro EQU/CALL a direcciones +absolutas de ROM Spectrum al portar ficheros del zx48k (grep hecho: +no queda ninguno en el runtime zx81sd). + +## Esquema de teclado nuevo: mayúsculas, CAPS LOCK y símbolos — 2026-07-04 + +| Fuente | Prefijo SD81 | Qué prueba | Resultado | +|---|---|---|---| +| `tests_debug/keytest.bas` | KEYTST | INKEY$ interactivo: imprime código ASCII + carácter de cada tecla | Verificado por simulación exhaustiva (ver abajo); pendiente de probar a mano en el emulador/hardware | + +El teclado físico del ZX81 no distingue mayúscula/minúscula por tecla +(ver [[zx81sd-keyboard-case]]): SHIFT+letra da un símbolo, no la +mayúscula de esa letra. Hasta ahora `keyscan.asm` solo devolvía +minúsculas siempre. Nuevo esquema (`src/lib/arch/zx81sd/runtime/io/ +keyboard/keyscan.asm`, reescrito de raíz): + +- Sin modificador: minúscula (igual que antes — comecoquitos, snake, + flights, row4 siguen funcionando sin tocar una línea, porque ninguno + usa SHIFT). +- `SHIFT + letra`: MAYÚSCULA de esa letra. +- `SHIFT + "2"`: conmuta CAPS LOCK persistente (mudo, no imprime nada). +- CAPS LOCK activo: minúscula pasa a mayúscula; SHIFT sigue dando + mayúscula igual (es un OR, no hay interacción, decisión tomada con el + usuario). +- `"."` sola: `.` +- `SHIFT + "."`: `,` (igual que en el ZX81 real). +- `"." + otra tecla`: el símbolo impreso en el teclado del ZX81 para esa + tecla (`:` con Z, `)` con O, RUBOUT con 0, etc. — la vieja tabla SHIFT + del ZX81, ahora alcanzable con "." en vez de con SHIFT, ya que SHIFT + se ha redefinido para dar mayúsculas). + +Requirió reescribir el escaneo: antes se paraba en la primera fila con +algo pulsado (bastaba con una tecla a la vez). Ahora hacen falta dos +lecturas de puerto dedicadas para SHIFT (fila 0) y "." (fila 7, columna +1), más un escaneo de las demás filas buscando una tercera tecla +excluyendo esas dos posiciones (rutina `FIND_OTHER`). El combo +`SHIFT+"2"` usa un byte de estado persistente con detección de flanco +para no conmutar varias veces mientras se mantiene pulsado. + +Verificado con un arnés de simulación Python (`z80`, ver metodología ya +usada para los bugs del heap): se llama a `__ZX81SD_KEYSCAN` directamente +inyectando por el callback de E/S los bits de fila exactos de cada +combinación (Z sola, SHIFT+Z, "."+Z, SHIFT+2 mantenido 4 polls seguidos, +etc.), sin pasar por la complejidad de un teclado real. Los offsets de +las tablas y del estado (`_KBD_*`, todos LOCAL al PROC, no aparecen en el +`.map`) se localizaron buscando el patrón de bytes de `UNSHIFT_TABLE` +("zxcvasdfg") en el binario y calculando el resto por desplazamiento fijo +(cada tabla ocupa 39 bytes). Los 12 casos de la tabla de diseño +coincidieron exactamente, incluido el debounce del CAPS LOCK. + +### Corrección 2026-07-04: el modificador "." se movió de keyscan a input.bas + +Al probarlo en el emulador, el usuario detectó que `"."+tecla` (pensado +para dar el símbolo del ZX81 pulsando ambas a la vez, como SHIFT+letra) +era impracticable desde `INPUT`: `PRIVATEInputWaitKey` compromete la +tecla `"."` en cuanto la detecta sola, sin dar tiempo a que la segunda +tecla llegue de verdad a la vez (a diferencia de SHIFT+letra, que sí se +puede sostener cómodamente con la otra mano). Diagnóstico correcto del +usuario: *"la gestión de los símbolos no debe ir en el keyscan sino en +INPUT.bas"*. + +Fix: `keyscan.asm` ahora trata `"."` como una tecla más — sin +modificador da `.`, con SHIFT da `,` (igual que el ZX81 real), sin +ninguna lógica de "tercera tecla" para el punto (se quitó por completo +la exclusión de la fila 7 en `FIND_OTHER`, ya no hace falta). Las tablas +`UNSHIFT_TABLE`/`SYMBOL_TABLE`/`CAPS_TABLE` se promovieron de `LOCAL` a +ámbito de fichero (prefijo `__ZX81SD_`) y se añadió una rutina nueva, +`__ZX81SD_SYMBOL_FOR(char)`, que hace la búsqueda inversa +UNSHIFT_TABLE→SYMBOL_TABLE dado un carácter ya decodificado. + +La composición de símbolos ahora vive en `stdlib/input.bas` como una +"tecla muerta" secuencial: al leer `"."`, la función `input()` lee la +SIGUIENTE tecla por separado (sin exigir simultaneidad) y llama a +`PRIVATEInputSymbolFor()`; si hay símbolo, lo añade; si no, añade el +punto literal y procesa la segunda tecla con normalidad (DEL, ENTER, o +un carácter más). + +Bug propio cazado durante la implementación (antes de que el usuario lo +viera): `"."` + `"0"` resuelve a RUBOUT (12) vía `SYMBOL_TABLE` (es el +símbolo real que el ZX81 imprime sobre la tecla "0"), lo que borraría el +carácter anterior al escribir cualquier decimal terminado en ".0" (muy +habitual: "3.0", "10.0"...). Se excluyó ese valor explícitamente en +`input.bas` — RUBOUT ya se alcanza sin ambigüedad con SHIFT+0 (sí es +cómodo de sostener a la vez). Verificado con simulación de programa +completo (tecleo scriptado "1",".","0",ENTER → `a$="1.0"` correctamente, +y "."," o",ENTER → `a$=")"`), no solo con la función aislada. + +`tests_debug/keytest.bas` (KEYTST) sigue siendo el tester interactivo de +`INKEY$`; `tests_debug/inputtest.bas` (INTEST) es el mismo mini test de +`INPUT()` de antes, ahora también sirve para probar la composición de +símbolos a mano. + +### Refinamiento 2026-07-04: punto dos veces seguidas = punto literal + +Con el diseño anterior, `"."` + una letra con símbolo asociado (p.ej. Z → +`:`) siempre resolvía a ese símbolo — no había forma de escribir +literalmente un punto seguido de esa letra. Pedido del usuario: pulsar +`"."` dos veces seguidas debe confirmar el primer punto como literal +(la coma redundante que antes salía de `"."+"."`, vía `SYMBOL_TABLE`, ya +no hace falta — sale directamente y sin ambigüedad con `SHIFT+"."`), y la +tecla que venga después se lee como una pulsación nueva sin combinar. +Así, para escribir ".Z" se teclea "." "." "Z". + +Implementado en `input.bas`: si la segunda tecla leída tras un "." es +también un ".", se añade el punto, se descarta la segunda pulsación (no +imprime nada por sí misma, `LastK=0`) y el bucle vuelve a leer una tecla +fresca. Verificado con la misma simulación de programa completo: +`"."+"."` → `a$="."`; `"."+"."+"z"` → `a$=".z"` (sin formar el símbolo +`:`). + +## scroll.bas — RESUELTO 2026-07-04 (librería nueva, ejemplo sin cambios) + +| Fuente | Prefijo SD81 | Qué prueba | Resultado | +|---|---|---|---| +| `examples/scroll.bas` | SCROLL | Los 4 scrolls pixel-a-pixel (Right/Down/Left/Up) sobre una ventana de 60×60 px, 30 vueltas | Simulado 1200M ticks sin HALT/RST38; pendiente de ver en el emulador (el ejemplo hace 1920 scrolls de hasta 100×100 px, tarda un rato) | + +`src/lib/arch/zx48k/stdlib/scroll.bas` no tenía override en zx81sd — se +usaba la versión de zx48k tal cual, y las 8 subs (`ScrollRight/Left/Up/ +Down` + sus variantes `*Aligned`) llaman todas a `call 22ACh`, la rutina +*PIXEL-ADD* de la ROM del Spectrum. En zx81sd no hay ROM mapeada: esa +dirección cae en pleno código compilado del programa, y el HALT +reportado (`RST 38` en la traza) era justo el байте que hubiera ahí por +casualidad. + +Fix: `src/lib/arch/zx81sd/stdlib/scroll.bas`, copia idéntica salvo las 8 +llamadas a `$22AC` sustituidas por `call PIXEL_ADDR` (nuestra propia +rutina, `runtime/pixel_addr.asm`, ya usada por `plot.asm`/`draw.asm` — +ver [[zx81sd-pixel-addr-contract]]). El contrato de registros es +IDÉNTICO al de la ROM (A=191, B=Y, C=X → HL=offset, A=X AND 7, destruye +B, preserva D/E), así que no hizo falta tocar ni una línea del cuerpo de +los bucles de scroll, solo el punto de llamada. `SP.PixelDown`/ +`SP.PixelUp` (de zx48k/runtime/SP/) no necesitaron copia: son aritmética +pura sobre `SCREEN_ADDR`, sin ROM, y ya funcionaban igual en zx81sd (se +resuelven por el mecanismo normal de fallback a zx48k cuando no hay +override). + +`examples/scroll.bas` no necesitó ningún cambio de fuente — como +`4inarow.bas`, el problema era enteramente de la librería, no del +programa. Añadido a `CAMBIOS_BASIC.md` con esa misma nota. + +## maskedsprites.bas — RESUELTO 2026-07-04 (cambio de fuente, no de librería) + +| Fuente | Prefijo SD81 | Qué prueba | Resultado | +|---|---|---|---| +| `examples/maskedsprites.bas` → `examples/sd81/maskedsprites_sd81.bas` | MASKED | Sprites enmascarados (AND+OR) con MSFS, 10 sprites animados | Simulado 1000M ticks sin HALT/RST38/escritura ilegal; PC avanza por un rango amplio de direcciones (no atascado) | + +A diferencia de `scroll.bas`, aquí el problema SÍ estaba en el propio +ejemplo (`WaitForNewFrame`, definida directamente en `examples/ +maskedsprites.bas`, no en la librería `cb/maskedsprites.bas`): hace +`EI` + `HALT` esperando la interrupción IM1 de 50Hz de la ROM del +Spectrum, comparando contra el contador `FRAMES` de la ROM en la +dirección absoluta `23672`. En zx81sd las interrupciones están +permanentemente deshabilitadas (todo el runtime corre con `DI`; el +vector `$0038` es solo una trampa `DI;HALT`, no un manejador real) — ese +`HALT` no despierta nunca. Confirmado con la traza: el simulador se +quedaba con `m.halted=True` exactamente en el `HALT` de `WaitForNewFrame` +tras ~31M ticks. + +Fix en `examples/sd81/maskedsprites_sd81.bas`: `WaitForNewFrame` reescrita +para usar `VSYNC_TICK` (`runtime/vsync.asm`, ya usada por `PAUSE`) en vez +de `EI+HALT` — sondea por puerto ($AFh) el contador de pulsos VSYNC +hardware del SD81 Booster, sin depender de interrupciones. El algoritmo +original hacía UN `HALT` inicial y luego un bucle que comprobaba +`FRAMES` SIN esperar de nuevo (confiaba en que la interrupción lo +siguiera incrementando en segundo plano); como en zx81sd nada lo +incrementa solo, el bucle llama a `VSYNC_TICK` explícitamente en cada +vuelta que le falte. `GetInterruptStatusInBorder` se dejó intacta (no se +llama nunca en el bucle principal, solo aparece comentada — se mantiene +únicamente para que la comprobación de compilación del final del fichero +no falle por "función no usada"). + +### Actualización 2026-07-04: MSFS portado de verdad al mapeador (bloque 7) + +El riesgo de `$5B5C`/`$7FFD` de arriba **ya no aplica**: a petición del +usuario ("¿por qué no usamos el bloque 7 que tenemos para bancos?") se +creó `src/lib/arch/zx81sd/stdlib/cb/maskedsprites.bas`, override +completo de la librería compartida (que sigue intacta, regla de +Boriel). Diseño (ver también `CAMBIOS_BASIC.md`): + +- **Hallazgo clave**: las funciones de MSFS (`RegisterSpriteImageInMSFS`, + `FindFirstUnusedBlockInMSFS`, etc.) son agnósticas de banco/dirección + — solo llaman a `GetBankPreservingRegs`/`SetBankPreservingINTs` y + leen/escriben la variable BASIC `MaskedSpritesFileSystemStart`. + Reescribiendo esas dos primitivas (usando el puerto `$E7` sobre el + **bloque 7**, `$E000-$FFFF` — reservado en nuestro mapa de memoria + justo para "banking de datos, mapas, sprites") y el cálculo de esa + dirección en `InitMaskedSpritesFileSystem()` (fija en `$E000` en vez + de "lo que quede hasta `$FFFF`", que asumía RAM plana Spectrum), el + resto del fichero (cientos de líneas de álgebra de bloques/bitmap) se + copió literalmente sin tocar una línea. +- `CheckMemoryPaging()` devuelve `0` (honesto: zx81sd no tiene doble + pantalla visible al estilo banco 5/7 del Spectrum) sin afectar a + MSFS, porque las funciones de MSFS no consultan esa función para + decidir si usar el banco — lo hacen incondicionalmente. +- `SetVisibleScreen`/`GetVisibleScreen`/`ToggleVisibleScreen`/ + `CopyScreen5ToScreen7`/`CopyScreen7ToScreen5`/`SetDrawingScreen5`/ + `SetDrawingScreen7`/`ToggleDrawingScreen` → stubs seguros (doble + buffer de pantalla real no está cubierto; código muerto en este + ejemplo dado que `memoryPaging=0`, pero ya no tocan `$5B5C`/`$7FFD` + por si alguien los llama directamente en el futuro). + +**Bug real encontrado durante la implementación** (no por el usuario, +cazado con el propio arnés de simulación): mi primer intento escribió +`SetBankPreservingINTs`/`GetBankPreservingRegs` en BASIC plano en vez de +ASM a mano. Rompía el contrato de registros documentado en el propio +fichero original ("Preserves: D, E, H, L") que el código ASM de +`RegisterSpriteImageInMSFS` y compañía da por hecho (por ejemplo, para +no perder `spriteImageAddr`, que llega en HL) — una función BASIC +compilada usa registros libremente por dentro sin ninguna garantía de +preservarlos. Resultado: los 6 sprites de prueba se registraban todos en +la MISMA dirección (`$0C07`) en vez de direcciones distintas. Se +reescribieron ambas primitivas en ASM a mano, con el mismo contrato +exacto que el original (solo tocan A, B, C). + +Verificado con simulación: las 6 direcciones de registro (`regHero0`, +`regFoe00`, `regFoe20-23`) salen correlativas cada 96 bytes exactos +(`$E010, $E070, $E0D0, $E130, $E190, $E1F0`, coincidiendo con +`$E000+n*96+16`), sin disparar `STOP`, y el bucle principal +(`WaitForNewFrame`) se alcanza repetidamente sin cuelgue tras 1000 +millones de ticks de simulación sin `HALT`/escritura ilegal. + +Limitación conocida del simulador Python usado en esta sesión: no +modela el mapeador de memoria (los `OUT` a `$E7` son no-op en la +simulación, toda la RAM se trata como plana) — no puede validar que el +intercambio de página *físico* funcione de verdad, solo que la lógica +Z80 es correcta asumiendo que sí. La validación definitiva es en el +emulador/hardware real. + +### Segunda vuelta 2026-07-04: seguía yéndose a HALT en hardware real + +Con el fix de arriba ya compilado, en el emulador real seguía disparando +`__STOP` (mismo síntoma: `RegisterSpriteImageInMSFS` devuelve 0). Traza +confirmó que el problema estaba en `SetBankPreservingINTs`, que hacía el +`OUT` al puerto `$E7` a mano en vez de llamar a `Map()` (mcu.bas): +escribía `A=7` (bloque, sin combinar con la página) con `B=página`. Mi +hipótesis inicial fue que el hardware podía estar en modo simple (donde +el byte de datos debe llevar página Y bloque combinados, +`(página AND 31)<<3 | bloque`, y con solo `A=7` se interpretaría como +página=0) — **el usuario corrigió esto**: el cargador SD81 deja el +mapeador en modo completo desde la línea `LOAD *MAP 7,63`, hasta el +siguiente reset, así que esa explicación concreta no cuadra (en modo +completo solo importan los 3 bits bajos de A, iguales en ambas +versiones). La causa exacta seguía sin confirmarse en el momento de +escribir esto. + +Se cambió `SetBankPreservingINTs` para llamar a `Map()` (código ya +probado en otros contextos) en vez de repetir la lógica del puerto a +mano, preservando D,E,H,L alrededor de la llamada con `push`/`pop` +manuales (`Map()` en sí no preserva nada). Tuvo un efecto secundario: +`Map()` dejó de estar referenciada desde BASIC en ningún sitio (solo +desde ASM a mano), y el eliminador de código muerto del compilador la +quitó del binario → `Undefined GLOBAL label '._Map'`. Se resolvió con +una llamada BASIC explícita y redundante a `Map(7, MaskedSprites_MSFS_Page)` +dentro de `InitMaskedSpritesFileSystem()` (comentada como tal — el +análisis de uso del compilador no cuenta las llamadas hechas desde ASM). + +Verificado de nuevo en simulación (mismas 6 direcciones correlativas, +sin `STOP`) — pero siguió fallando en el emulador real. El cambio a +`Map()` era, como señaló el usuario, un no-op ("el Map de la biblioteca +hace exactamente lo mismo que el out"). + +### Causa raíz REAL (tercera vuelta, 2026-07-04): el FSB nunca se inicializa + +La traza del usuario mostraba `FindFirstUnusedBlockInMSFS` recorriendo +el free-space bitmap COMPLETO (bucle FIND-INT, 254 líneas de RRCA/DEC E) +y saliendo por la rama `full` (`SCF/RET` → `JR C` → `LD HL,0`): **todos +los bloques aparecían como "ocupados"**. `__EQ16` (el sospechoso +inicial) funcionaba perfectamente — `regHero0` realmente era 0. + +Causa: **ni nuestra versión ni la original de zx48k limpian jamás el +FSB** (los bytes del bitmap en `start+2..start+1+l`). En el Spectrum no +hace falta: el test de RAM de la ROM deja toda la memoria a cero en el +arranque, así que el bitmap nace "todo libre" gratis. En zx81sd la +página del bloque 7 llega con basura de fábrica → todos los bits a 1 → +"sin bloques libres" → `RegisterSpriteImageInMSFS` devuelve 0 → `STOP`. + +**Por qué el simulador Python dio falso OK dos veces**: su RAM también +nace a ceros, igual que la del Spectrum tras el test de ROM — +exactamente la condición que oculta el bug. Lección de metodología +incorporada al arnés: para validar código que lee memoria no +inicializada, rellenar toda la RAM no cargada con basura (`$FF`) antes +de simular. Con RAM sucia, el binario sin fix reproduce el `STOP` (el +modo de fallo de la traza) y el binario con fix pasa completo (6 +registros correctos, bucle principal alcanzado). + +Fix (3 líneas + comentario en `InitMaskedSpritesFileSystem`): bucle +`FOR j = start+2 TO start+1+l: poke j,0: NEXT` tras calcular el tamaño +del FSB. + +También se verificó por el camino, con `examples/sd81/block7test.bas` +(prefijo BLOCK7), que el mapeador funciona perfectamente: patrones +distintos escritos en páginas 20 y 63 del bloque 7 sobreviven al +intercambio de página (contenido independiente por página). El +mapeador nunca fue el problema. + +### Cuarta vuelta 2026-07-04: sprites como líneas verticales — página residente + +Con el fix del FSB, en el emulador real MSFS ya inicializaba bien +(pantalla: `Init MSFS at 57344`, `Free Blocks = 85`, y los 6 registros +con los valores EXACTOS que predijo la simulación) pero los sprites se +dibujaban como líneas verticales en vez de sus gráficos. + +Causa (descuido de diseño de este override, no del original): +`SaveBackgroundAndDrawSpriteRegisteredInMSFS` — el que dibuja en el +bucle principal y el que fabrica las imágenes desplazadas bajo demanda — +accede a la MSFS **sin envolver con Get/SetBank**. En el diseño original +no lo necesita: en 128K el banco 7 se queda mapeado en `$c000` +(`SetDrawingScreen7`) y en 48K la MSFS está en RAM plana siempre +visible. Nuestra primera versión "liberaba" el bloque 7 de vuelta a la +página 63 al restaurar tras `Init`/`Register...` → el dibujo leía +máscaras y gráficos de la página 63 (basura) → líneas verticales. El +simulador no podía detectarlo: sin mapeador modelado, página 20 y 63 +son la misma RAM plana. + +Fix: la página de MSFS queda **residente** en el bloque 7 desde el +init — `SetBankPreservingINTs` con valor ≠ 7 solo anota el número, no +desmapea (el "liberar a página 63" era invención de este override, +nada lo necesita). Documentado en la cabecera de la librería: si un +programa usa el bloque 7 para su propio banking, debe remapear su +página él mismo y llamar a `SetBankPreservingINTs(7)` antes de volver a +usar MSFS. diff --git a/docs/zx81sd/PRECAUCIONES.md b/docs/zx81sd/PRECAUCIONES.md new file mode 100644 index 000000000..e1c543fd6 --- /dev/null +++ b/docs/zx81sd/PRECAUCIONES.md @@ -0,0 +1,184 @@ +# Precauciones al escribir o portar software para zx81sd + +zx81sd hace que ZX BASIC genere binarios que "parecen" un Spectrum (la +interfaz SD81 Booster emula su pantalla, y buena parte de la stdlib +compartida asume convenciones del Spectrum), pero **no hay ROM del +Spectrum en ningún sitio**: no hay `RST $28` de la ROM, no hay rutinas +en direcciones fijas, no hay sysvars del Spectrum en `$5C00+`. Casi +todos los bugs de este port han venido de código (de examples/ oficiales +o de la stdlib compartida) que asume silenciosamente alguna de estas +cosas. Antes de portar algo, repasa esta lista. + +## 1. Nunca hay ROM: cuidado con direcciones absolutas y sysvars + +Cualquier `POKE`/`PEEK`/`CALL` a una dirección numérica fija +(23675, 23658, $22AC, $0DFE...) casi seguro que es una sysvar o rutina +de la **ROM del Spectrum**, que en zx81sd no existe: esa dirección cae +en RAM libre, o peor, en pleno código compilado del programa — +ejecutarla o interpretarla como dato produce corrupción silenciosa, +gráficos erróneos, o un `HALT`/reinicio salvaje muy difícil de +relacionar con la causa (varios bugs de este port tardaron sesiones +enteras en diagnosticarse por esto). + +- **Sysvars del Spectrum → sysvars de zx81sd**: la tabla de + equivalencias está en + [`../../src/lib/arch/zx81sd/runtime/sysvars.asm`](../../src/lib/arch/zx81sd/runtime/sysvars.asm) + (todas viven en `$8000+`, no en `$5C00+`). Ejemplos ya resueltos: + `UDG` (23675 → `$8002`), `COORDS` (23677/23678 → `$8004`/`$8005`). + Ver [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md) para el detalle línea a + línea de cada caso ya encontrado. +- **Rutinas de ROM llamadas directamente** (`call $22AC` = PIXEL-ADD, + `call $0DFE` = CL-SC-ALL/scroll, `RST $28` = calculador FP...): si el + fuente o una librería compartida hace esto, hace falta un override en + `src/lib/arch/zx81sd/` que sustituya la llamada por la rutina propia + con el **mismo contrato de registros** que la de la ROM (ver más + abajo). Ejemplo resuelto: `stdlib/scroll.bas`. +- **`grep` preventivo**: al portar un fichero de `zx48k/` a `zx81sd/`, + buscar `EQU 0[0-9A-F]` / `call 0x` / literales de 4-5 dígitos + sospechosos antes de darlo por bueno, no solo cuando algo falla. + +## 2. Contratos de registros de las rutinas ASM: son sagrados + +Varias rutinas del runtime tienen contratos de preservación de +registros explícitos y no negociables porque el código que las llama +(heredado de zx48k, no tocable) depende de ellos al pie de la letra. +Ejemplos: + +- `PIXEL_ADDR` (`runtime/pixel_addr.asm`): A=191, B=Y, C=X → HL=offset, + A=X AND 7; **destruye B, preserva D y E**. `draw.asm` guarda la + coordenada Bresenham en D alrededor de la llamada confiando en esto + literalmente — romperlo (como pasó una vez con un intento que usaba D + de scratch) corrompe cualquier línea con componente vertical sin + tocar para nada las horizontales, lo que despista mucho a la hora de + diagnosticar. +- `GetBankPreservingRegs`/`SetBankPreservingINTs` (MSFS, + `cb/maskedsprites.bas`): contrato documentado "preserva D,E,H,L". + Escribir el reemplazo en BASIC plano en vez de ASM a mano rompe esto + sin ningún aviso del compilador (el código BASIC generado usa + registros libremente por dentro) — un bug real de esta clase hizo que + 6 sprites de prueba se registraran todos en la misma dirección + incorrecta. **Nota**: el port de MSFS/`maskedsprites.bas` sigue en + proceso (aún no funciona del todo bien) — este ejemplo concreto sirve + para ilustrar el tipo de bug, no como confirmación de que la librería + ya esté terminada. + +**Regla práctica**: si vas a sustituir una rutina ASM que tiene un +contrato de registros documentado (o que se puede inferir mirando quién +la llama y qué asume), reimplaza en ASM a mano preservando exactamente +ese contrato. Una función BASIC (`SUB`/`FUNCTION`), por sencilla que +parezca, NO es un reemplazo válido salvo que el contrato sea "ninguno". + +## 3. El teclado es el del ZX81, no el del Spectrum + +El SD81 Booster no tiene teclado Spectrum: reescanea el teclado físico +de 40 teclas del ZX81 (`runtime/io/keyboard/keyscan.asm`). Diferencias +que importan al portar/escribir código: + +- Pulsación directa de una letra: minúscula. Con `SHIFT+letra`: + MAYÚSCULA de esa letra — exactamente igual que en un Spectrum real, a + diferencia del ZX81 original (donde `SHIFT+letra` daba un símbolo, no + una mayúscula). Esta redefinición es una decisión de diseño de este + port, ver [MAP.md](MAP.md) sección "Esquema de teclado nuevo". En + consecuencia, **sí se puede escanear/comparar `INKEY$` contra + mayúsculas de letra** (`IF INKEY$="S"`, pensado para jugarse con + `SHIFT` sostenido al estilo Spectrum) sin ninguna limitación de + hardware: basta con pulsar `SHIFT+S`. +- `SHIFT+"2"` alterna un CAPS LOCK persistente. +- `"."` es una tecla normal (`.` sin shift, `,` con shift). Los símbolos + del ZX81 original asociados a cada tecla (`:` en Z, `)` en O, etc.) se + alcanzan con la secuencia `"." + tecla` **solo desde `INPUT()`** (tecla + muerta gestionada en `stdlib/input.bas`), no desde `INKEY$` a pelo — + no hay forma de "sostener ambas a la vez" con fiabilidad en este + teclado, así que la composición se hace pulsando `.` primero y la + segunda tecla después. Pulsar `.` **dos veces seguidas** confirma el + primer punto como literal y descarta cualquier combo: la tecla que + venga después de ese segundo punto se lee como una pulsación nueva, + sin combinar con nada (permite escribir cualquier tecla justo después + de un punto sin arriesgarse a formar un símbolo por accidente). + +## 4. No hay interrupciones: nunca esperes un `HALT`/`EI` para sincronizar + +El runtime de zx81sd corre permanentemente con interrupciones +deshabilitadas (`DI`); el vector `$0038` es solo una trampa `DI;HALT`, +no un manejador de interrupción real. Cualquier código (típicamente +código ASM inline de un ejemplo, no de la stdlib) que haga `EI` seguido +de `HALT` esperando el pulso de 50Hz de la ROM del Spectrum **se cuelga +para siempre** — no hay nada que lo despierte. + +- **Sustituto**: `VSYNC_TICK` (namespace `core`, en + `runtime/vsync.asm`) sondea por puerto el contador de pulsos VSYNC + real del hardware SD81 Booster. Ya lo usa `PAUSE` internamente. +- Al llamarlo desde un bloque `ASM ... END ASM` que no esté ya dentro de + `push namespace core`, hay que usar el prefijo completo: + `call .core.VSYNC_TICK` (si se omite el prefijo, el compilador da + `Undefined GLOBAL label '.VSYNC_TICK'` — error ya visto más de una + vez en este port). +- Un contador que antes se incrementaba solo por la interrupción en + segundo plano (`FRAMES`/23672 en el Spectrum) hay que actualizarlo a + mano llamando a `VSYNC_TICK` explícitamente en cada vuelta del bucle + de espera, no solo una vez al principio. + +## 5. Namespaces y mangling de etiquetas ASM + +Un `DIM X` o `SUB`/`FUNCTION X` de BASIC se traduce a la etiqueta ASM +`_X` (un solo guion bajo), **salvo** que el fichero envuelva su código +en `push namespace core ... pop namespace`, en cuyo caso hay que +referenciarla desde fuera como `.core._X` (variables) o `.core.X` +(funciones/rutinas). Confundir esto en cualquier dirección produce +`Undefined GLOBAL label`. Si un fichero de este port no usa namespacing +en ningún otro sitio, no hace falta envolver un bloque `ASM` nuevo en +`push namespace core` solo porque otro fichero (como `vsync.asm`) sí lo +use — basta con prefijar la referencia puntual. + +## 6. El eliminador de código muerto no ve las llamadas desde ASM a mano + +El análisis de "¿esto se usa?" del compilador solo cuenta llamadas +hechas con sintaxis BASIC (`Foo(x)`). Una `SUB`/`FUNCTION`/variable +BASIC referenciada **solo** desde un bloque `ASM ... END ASM` (p. ej. +`call _Foo`) puede ser eliminada como código muerto, dando +`Undefined GLOBAL label '._Foo'` al enlazar — el símbolo nunca llegó a +existir en el binario final. Dos salidas: + +- Si es un dato puro (un byte de estado, por ejemplo), declararlo como + ASM puro (`ASM \n _Label: \n DEFB 0 \n END ASM` a nivel de fichero), + no como `DIM`. +- Si es una función que de verdad hace falta que sea BASIC (porque + llama a otras cosas de la stdlib), añadir una llamada BASIC real + (aunque sea redundante/no estrictamente necesaria en ese punto) en + algún sitio alcanzable del código, para que el análisis de uso la + cuente. + +## 7. Metodología de depuración sin hardware + +Para diagnosticar sin gastar ciclos de prueba-error en el emulador o el +hardware real, este port usa simulación directa del binario con el +paquete `z80` de Python. Dos lecciones ya aprendidas por las malas +(documentadas con más detalle en [MAP.md](MAP.md)): + +- Comprobar el PC periódicamente cada N ticks gruesos puede dar falsos + "atascado" si justo cae siempre en el mismo punto de un bucle; usar + breakpoints reales (comparar `m.pc` contra la dirección exacta, + sacada del `.map`) o chunks de tick más finos. +- La RAM del simulador nace a **ceros**, igual que la del Spectrum tras + el test de RAM de su ROM. Esto oculta bugs de memoria no inicializada + (dio dos falsos "OK" seguidos en el bug del free-space-bitmap de + MSFS). Para validar código que lee memoria que no inicializa él + mismo, rellenar toda la RAM no cargada con `0xFF` antes de cargar el + binario, para reproducir las condiciones reales de hardware/tarjeta. +- El simulador **no modela el mapeador de memoria** (`OUT` al puerto + `$E7` es un no-op): puede validar que la lógica Z80 es autoconsistente, + pero no que el intercambio de página físico funcione de verdad — eso + solo se confirma en el emulador (EightyOne) o en hardware real. +- Si un binario falla en EightyOne pero la simulación Python lo ejecuta + limpio, sospecha primero del emulador (ver el bug de traps de cinta ya + encontrado y corregido en `Eightyone2/src/ZX81/rompatch.cpp`, en el + repositorio del emulador, no en este) antes que del runtime — en + hardware real esos traps no existen. + +## Ver también + +- [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md) — catálogo de cambios de fuente + ya necesarios en ejemplos oficiales, con el patrón general a buscar + en cualquier ejemplo nuevo. +- [MAP.md](MAP.md) — bitácora técnica completa, bug a bug, con las + trazas de investigación. diff --git a/docs/zx81sd/README.md b/docs/zx81sd/README.md new file mode 100644 index 000000000..e8d7dd7de --- /dev/null +++ b/docs/zx81sd/README.md @@ -0,0 +1,79 @@ +# Arquitectura `zx81sd`: ZX81 + SD81 Booster + +`zx81sd` es una arquitectura de este compilador para un **ZX81 real con +la tarjeta [SD81 Booster](https://www.sd81.eu/)** (una interfaz que +añade pantalla tipo Spectrum, sonido AY/beeper, mapeador de memoria por +páginas y acceso a tarjeta SD). + +Todo el código específico de esta arquitectura vive exclusivamente bajo: + +``` +src/arch/zx81sd/ (backend del compilador) +src/lib/arch/zx81sd/ (runtime ASM + stdlib BASIC) +``` + +## Regla de oro del port + +Este compilador es compartido por todas las arquitecturas (zx48k, +zx128k, zxnext...). **El port de zx81sd nunca modifica el frontend ni +la stdlib/runtime compartidos.** El mecanismo de resolución de +`#include`/`#require` busca primero en `src/lib/arch/zx81sd/`, y si no +encuentra el fichero cae automáticamente en `src/lib/arch/zx48k/` (el +fichero compartido). Por eso muchos overrides de zx81sd son copias +completas de la versión zx48k con solo unas pocas líneas cambiadas: hay +que copiar el fichero entero, no un parche — un override parcial +simplemente no existe como concepto aquí. + +Antes de tocar cualquier cosa fuera de `zx81sd/`, para: seguramente hay +una forma de resolverlo con un override. + +## Estado del port + +Funcionalmente completo desde 2026-07-02: FP (RST $28 propio), +gráficos (PLOT/DRAW/arcos/CIRCLE), sonido (BEEP y PLAY sobre AY ZonX y +beeper), teclado (INKEY$/INPUT sobre el teclado físico del ZX81), +joystick, la librería MCU completa (ficheros, RTC/BAT, voz, mapeador de +memoria...) y LOAD/SAVE/VERIFY...CODE contra SD. + +Pendiente / sin auditar: el resto de utilidades de pantalla de la +stdlib compartida (`winscroll`, `putchars`, `puttile`, `screen`, +`print42/64`...) — puede que alguna dependa de la ROM del Spectrum +igual que `scroll.bas` (ya arreglado, ver [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md)). +El port de `maskedsprites.bas`/MSFS (sprites enmascarados sobre el +mapeador de memoria) también está en proceso, aún sin terminar de +funcionar bien. + +## Documentación + +- **[USO.md](USO.md)** — cómo compilar un programa, empaquetarlo para + el ZX81 y cargarlo desde la tarjeta SD. +- **[PRECAUCIONES.md](PRECAUCIONES.md)** — qué hay que tener en cuenta + al escribir o portar software para esta arquitectura (mapa de + memoria, sysvars, teclado, cosas que NO existen aquí aunque existan + en Spectrum). +- **[CAMBIOS_BASIC.md](CAMBIOS_BASIC.md)** — qué cambios de fuente + BASIC hizo falta para portar cada ejemplo oficial de `examples/` (con + el porqué de cada uno). Punto de partida obligado antes de portar un + ejemplo nuevo: casi siempre es uno de los patrones ya catalogados + ahí. +- **[MAP.md](MAP.md)** — bitácora técnica detallada de todos los bugs + encontrados y corregidos durante el port (runtime ASM, no fuentes + BASIC), con la traza de investigación de cada uno. Es el documento a + consultar cuando algo falla en tiempo de ejecución de forma que + recuerda a un bug ya resuelto. + +## Ejemplos + +Los programas de ejemplo ya adaptados/probados para esta arquitectura +están en [`examples/sd81/`](../../examples/sd81/) (junto al resto de +`examples/` del compilador). El detalle de qué hubo que tocar en cada +uno, y por qué, está en [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md). + +## Herramientas de empaquetado y pruebas + +El empaquetador (`split_sd81.py`, parte binario plano en páginas de +8KB + genera el cargador `.p` para el ZX81) y el conjunto más amplio de +fuentes de depuración/diagnóstico usados durante el desarrollo del port +viven en un repositorio complementario, no en este. Ver +[USO.md](USO.md) para el flujo completo de compilar → empaquetar → +cargar. diff --git a/docs/zx81sd/USO.md b/docs/zx81sd/USO.md new file mode 100644 index 000000000..25438cfd3 --- /dev/null +++ b/docs/zx81sd/USO.md @@ -0,0 +1,88 @@ +# Uso: compilar, empaquetar y cargar un programa en zx81sd + +## 1. Compilar + +Desde la raíz de este repositorio: + +``` +python -m src.zxbc.zxbc --arch zx81sd -o -M +``` + +- `--arch zx81sd` selecciona el backend y los overrides de esta + arquitectura (ver [README.md](README.md) — regla de oro del port). +- `-M ` es opcional pero muy recomendable: genera el mapa de + símbolos (dirección de cada label ASM/BASIC), imprescindible para + depurar con un emulador o simular el binario (ver [MAP.md](MAP.md) + para ejemplos de arneses de simulación en Python). +- Ejemplos transcritos de Sinclair BASIC clásico (arrays/strings + 1-based) suelen necesitar además `--string-base 1 --array-base 1` + (ver [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md), ejemplo `comecoquitos.bas`). + +También existe `python zxbc.py --arch zx81sd -f bin -o ` +(script de entrada tradicional); ambas formas son equivalentes. + +## 2. Empaquetar para el ZX81 + +El binario que produce el compilador es una imagen plana de hasta 48KB +(bloques 0-5 del mapeador SD81). El ZX81 solo puede cargar de golpe lo +que quepa en su propia RAM visible, así que hay que partir ese binario +en páginas de 8KB y generar un cargador BASIC que las vaya metiendo en +la tarjeta SD81 Booster una a una, remapeando el mapeador de memoria +entre página y página. + +Esto lo hace `split_sd81.py`, una herramienta que vive en el +repositorio complementario de pruebas del port (no en este +repositorio del compilador): + +``` +python split_sd81.py [PREFIJO] +``` + +- `PREFIJO` (opcional) es el nombre base de los ficheros de salida, en + el charset del ZX81 (letras, dígitos, sin guion bajo). Si se omite, + se deriva del nombre del `.bin` de entrada. +- Genera: + - `P8.BIN`, `P9.BIN`, ... — una por cada 8KB del + binario (página SD81 8 = bloque 0, 9 = bloque 1, etc.) + - `_loader.txt` — el listado BASIC del cargador en texto + plano (para leer/depurar). + - `.P` — el mismo cargador, ya tokenizado, listo para + cargar y ejecutar en el ZX81 (`LOAD ""` desde la SD). + +El cargador generado usa `LOAD THEN CLEAR`, `LOAD *MAP` y +`LOAD FAST ... CODE`, extensiones del firmware del SD81 Booster (no +existen en la ROM original del ZX81). Hace lo siguiente, en orden: + +1. Reserva memoria (`CLEAR`) y carga `BOOT1.BIN` (el stage 1, fijo para + todos los programas, también en el repositorio complementario). +2. Por cada página del binario: mapea el bloque 7 a esa página física + (`LOAD *MAP 7,`) y vuelca los datos ahí (`LOAD FAST ... CODE + 57344`, la ventana del bloque 7 en `$E000`). +3. Al terminar, deja el mapeador en modo "página completa" (`LOAD *MAP + 7,63`) — a partir de aquí el mapeador ya no vuelve a modo simple + hasta el siguiente reset — y salta a `BOOT1.BIN` (`RAND USR 24576`), + que hace el mapeo definitivo de los bloques 0-5 y arranca el + programa. + +## 3. Copiar a la tarjeta SD + +Copia a la SD, junto al resto de tu colección: + +- `BOOT1.BIN` (una sola vez, es el mismo para todos los programas) +- Todas las `P.BIN` del programa +- `.P` + +Y en el ZX81 (o en EightyOne apuntando a la imagen de la SD): `LOAD ""` +y selecciona ``. + +## 4. Depurar sin hardware: simulación con Python + +Para diagnosticar cuelgues, HALTs o resultados incorrectos sin tener +que probar en el emulador o el hardware real cada vez, el desarrollo de +este port ha usado el paquete `z80` de Python (`pip install z80`) para +simular el binario plano directamente. La metodología completa +(incluida una trampa importante: la RAM del simulador nace a ceros, lo +que puede ocultar bugs de memoria no inicializada que sí aparecen en +hardware real) está documentada en [MAP.md](MAP.md) — sección "Heap en +$8100 + traps de cinta de EightyOne" y las notas de metodología del bug +de MSFS/maskedsprites. diff --git a/examples/sd81/block7test.bas b/examples/sd81/block7test.bas new file mode 100644 index 000000000..7c0104d68 --- /dev/null +++ b/examples/sd81/block7test.bas @@ -0,0 +1,63 @@ +' BLOCK7TEST -- verifica si el mapeador ($E7) mapea de verdad la +' pagina 20 al bloque 7 ($E000-$FFFF), sin nada de MSFS por medio. +' Escribe un patron dependiente de direccion, cambia a otra pagina y +' vuelve, y comprueba que el contenido sigue ahi (paginas distintas = +' contenido independiente) y que el patron se lee correctamente. + +#include + +DIM addr AS UINTEGER +DIM v, expected AS UBYTE +DIM errors AS UINTEGER + +CLS +PRINT "BLOCK7TEST" +PRINT + +' Paso 1: mapear pagina 20 y rellenar con patron +Map(7, 20) +PRINT "Escribiendo pagina 20..." +FOR addr = $E000 TO $E0FF + v = addr BAND 255 + POKE addr, v +NEXT addr + +' Paso 2: cambiar a pagina 63 (la "libre") y rellenar con OTRO patron +Map(7, 63) +PRINT "Escribiendo pagina 63..." +FOR addr = $E000 TO $E0FF + v = 255 - (addr BAND 255) + POKE addr, v +NEXT addr + +' Paso 3: releer pagina 63 (deberia tener el segundo patron) +errors = 0 +FOR addr = $E000 TO $E0FF + expected = 255 - (addr BAND 255) + v = PEEK(addr) + IF v <> expected THEN + errors = errors + 1 + IF errors <= 3 THEN PRINT "pagina 63 mal en "; addr; ": "; v; "<>"; expected + END IF +NEXT addr +PRINT "Pagina 63 tras releer: "; +IF errors = 0 THEN PRINT "OK" ELSE PRINT errors; " errores" + +' Paso 4: volver a pagina 20 y comprobar que SIGUE el primer patron +' (si esto falla con "0", el bloque 7 no esta guardando datos por +' pagina de verdad -- todas las paginas comparten la misma RAM) +Map(7, 20) +errors = 0 +FOR addr = $E000 TO $E0FF + expected = addr BAND 255 + v = PEEK(addr) + IF v <> expected THEN + errors = errors + 1 + IF errors <= 3 THEN PRINT "pagina 20 mal en "; addr; ": "; v; "<>"; expected + END IF +NEXT addr +PRINT "Pagina 20 tras volver: "; +IF errors = 0 THEN PRINT "OK" ELSE PRINT errors; " errores" + +PRINT +PRINT "FIN" diff --git a/examples/sd81/flights_sd81.bas b/examples/sd81/flights_sd81.bas new file mode 100644 index 000000000..90bb1f693 --- /dev/null +++ b/examples/sd81/flights_sd81.bas @@ -0,0 +1,129 @@ +#include + +0 DIM kk, c, wx, wy, gz, gy, gx, rw, y1, y2, y3, y4, pow, gc, rb, ll, yc, ad, st, rl, bc, nc, pt, px, vz, vy, vx, n AS Float +1 BORDER 1: PAPER 1: INK 7: CLS +2 LET wy=0: LET wx=0: LET gz=0: LET gy=0: LET gx=0 +5 LET rw=0: LET y1=120: LET y2=120: LET y3=40: LET y4=40: LET pow=0: LET gc=0: LET rb=0: LET ll=0: LET yc=0: LET ad=0: LET st=0: LET rl=0: LET bc=0: LET nc=0: LET pt=0: LET px=0: LET vz=0: LET vy=0: LET vx=0 +110 GO TO 5000 +500 LET ra=ad*c: LET vx=as0*SIN ra +510 LET vy=as0*COS ra: RETURN +1000 LET pz=pz+gz: LET py=py+gy: LET px=px+gx +1025 IF st=1 THEN PRINT OVER 1;AT 4,12;"S T A L L": LET st=0: GO TO 1040: END IF +1030 IF as0<30 THEN GO SUB 1500: END IF +1040 LET ad=ad+rl: IF ad<0 THEN LET ad=ad+360: END IF +1050 IF ad>359 THEN LET ad=ad-360: END IF +1060 LET vz=as0*SIN (pt*c)-10+as0/15 +1070 LET gz=vz: LET gy=vy+wy: LET gx=vx+wx +1080 IF vy=0 THEN LET gd=-PI/2: GO TO 1100: END IF +1090 LET gd=-ATN (vx/vy)/c +1100 GO SUB 500 +1110 RETURN +1500 LET st=1: PRINT OVER 1;AT 4,12;"S T A L L": FOR m=1 TO 4: FOR n=20 TO -20 STEP -4: BEEP .01,n: NEXT n: NEXT m +1510 LET rl=INT (RND*21)-9: LET pt=-21-INT (RND*5) +1520 RETURN +2000 IF gc<>0 THEN GO SUB 2200: END IF +2190 LET as0=as0+16*(tc*30-as0-8*pt)/as0: GO SUB 2200: GO TO 2205 +2200 PLOT 35,50+16: DRAW OVER 1;15*SIN (as0*PI/200),15*COS (as0*PI/200): RETURN +2205 IF gc<>0 THEN PLOT 155,50+16: DRAW OVER 1;10*SIN (tn*PI/5),10*COS (tn*PI/5): PLOT 155,50+16: DRAW OVER 1;15*SIN (un*PI/500),15*COS (un*PI/500): END IF +2210 LET tn=pz/1000: LET un=pz-1000*INT tn: PLOT 155,50+16: DRAW OVER 1;10*SIN (tn*PI/5),10*COS (tn*PI/5): PLOT 155,50+16: DRAW OVER 1;15*SIN (un*PI/500),15*COS (un*PI/500) +2220 IF gc<>0 THEN GO SUB 2230: END IF +2225 IF pow=-1 AND tc>.2 THEN LET tc=tc-.2: END IF +2226 IF pow=1 AND tc<8.8 THEN LET tc=tc+.2: END IF +2228 GO SUB 2230: GO TO 2240 +2230 PLOT 215,50+16: DRAW OVER 1;15*SIN (tc*PI/5),15*COS (tc*PI/5): RETURN +2240 PRINT AT 21,2;ABS INT ad;" " +2250 IF py=0 THEN LET rb=0: GO TO 2260: END IF +2255 LET rb=ATN (px/py)/c: IF py>0 THEN LET rb=rb+180: END IF +2260 IF rb<0 THEN LET rb=rb+360: END IF +2270 PRINT AT 21,10;INT rb;" ";AT 21,18;ABS INT px;" " +2280 PRINT AT 21,25;INT (SQR (py*py+px*px));" " +2290 IF (y1<=110 AND y2<=110) OR (y1>=130 AND y2>=130) THEN GO TO 2300: END IF +2295 IF gc<>0 THEN PLOT OVER 1;x1,168+16-y1: DRAW OVER 1;x2-PEEK $8004,168+16-y2-PEEK $8005: END IF +2300 LET yc=120+(pt/3): LET x1=80: LET x2=110: LET y1=yc+17*TAN (rl*2*c): LET y2=yc-17*TAN (rl*2*c) +2310 IF (yc<110 OR yc>130) AND rl=0 THEN GO TO 2376: END IF +2320 IF y1<110 THEN LET x1=95-(95-x1)*(110-yc)/(y1-yc): LET y1=110: GO TO 2340: END IF +2330 IF y1>130 THEN LET x1=95-(95-x1)*(130-yc)/(y1-yc): LET y1=130: END IF +2340 IF y2<110 THEN LET x2=95-(95-x2)*(110-yc)/(y2-yc): LET y2=110: GO TO 2360: END IF +2350 IF y2>130 THEN LET x2=95-(95-x2)*(130-yc)/(y2-yc): LET y2=130: END IF +2360 IF x1<80 OR x2>110 THEN GO TO 2376: END IF +2370 PLOT OVER 1;x1,168+16-y1: DRAW OVER 1;x2-PEEK $8004,168+16-y2-PEEK $8005 +2376 IF (rl=rr AND pp=pt) THEN GO TO 2500: END IF +2377 IF (y3<=2 AND y4<=2) OR (y3>=90 AND y4>=90) THEN GO TO 2380: END IF +2378 IF gc<>0 THEN PLOT OVER 1;x3,176+16-y3: DRAW OVER 1;x4-PEEK $8004,(176+16-y4)-PEEK $8005: END IF +2380 LET yc=33+pt*4: LET x3=11: LET x4=244: LET y3=yc+118*TAN (rl*2*c): LET y4=yc-118*TAN (rl*2*c) +2390 IF (yc<2 OR yc>90) AND rl=0 THEN GO TO 2450: END IF +2400 IF y3<2 THEN LET x3=128-(128-x3)*(2-yc)/(y3-yc): LET y3=2: GO TO 2420: END IF +2410 IF y3>90 THEN LET x3=128-(128-x3)*(90-yc)/(y3-yc): LET y3=90: END IF +2420 IF y4<2 THEN LET x4=128-(128-x4)*(2-yc)/(y4-yc): LET y4=2: GO TO 2440: END IF +2430 IF y4>90 THEN LET x4=128-(128-x4)*(90-yc)/(y4-yc): LET y4=90: END IF +2440 IF x3<11 OR x4>244 THEN GO TO 2500: END IF +2445 OVER 1: PLOT x3,176+16-y3: DRAW x4-PEEK $8004,(176+16-y4)-PEEK $8005: OVER 0 +2450 REM +2500 GO SUB 8000 +2505 IF gc=0 THEN LET gc=1: END IF +2510 LET rr=rl: LET pp=pt: RETURN +3000 LET pow=0: LET k$=INKEY$: IF k$="" THEN RETURN: END IF +3010 IF k$="s" THEN LET pow=pow-1: END IF +3020 IF k$="f" THEN LET pow=pow+1: END IF +3030 IF k$="q" THEN LET pt=pt+1: END IF +3040 IF k$="a" THEN LET pt=pt-1: END IF +3050 IF k$="o" AND rl>-30 THEN LET rl=rl-1: END IF +3060 IF k$="p" AND rl<30 THEN LET rl=rl+1: END IF +3070 RETURN +5000 LET pp=-1: LET rr=-1 +5010 LET c=PI/180: LET py=-20000: LET pz=2000: LET as0=150 +5020 PRINT AT 22,0; "INPUT WIND SPEED (1-50) MPH": LET xx0$=INPUT(10): LET x0=VAL(xx0$): PRINT AT 22,0; TAB 30; +5025 IF x0>50 OR x0<1 THEN GO TO 5020: END IF +5030 PRINT AT 22,0; "WIND DIRECTION (0-359) DEGREES": LET xx1$=INPUT(10): LET x1=VAL(xx1$): PRINT AT 22,0; TAB 30; +5035 IF x1>359 OR x1<0 THEN GO TO 5030: END IF +5040 LET x0=x0/3 +5050 PRINT "WIND SPEED= ";3*x0;" M/S": PRINT "DIRECTION = ";x1;" DEGREES" +5055 PAUSE 100: CLS +5060 LET wy=-x0*COS (x1*c) +5070 LET wx=-x0*SIN (x1*c) +5080 LET gz=vz: LET gy=vy+wy: LET gx=vx+wx +5090 LET tc=5 +5100 LET rt=3: LET tp=5: LET wr=50 +5110 PLOT 10,175+16: DRAW 235,0: DRAW 0,-90: DRAW -235,0: DRAW 0,90 +5120 FOR kk=0 TO 3: CIRCLE 35+kk*60,50+16,20: NEXT kk +5130 PRINT AT 12,2;"SPEED HORZN ALT RPM" +5150 PRINT AT 20,0;"BEARING RUNWAY DRIFT DISTANCE" +5170 PLOT 87,50+16: DRAW 5,0: DRAW 3,-3: DRAW 3,3: DRAW 5,0 +5180 LET x=35: LET y=50: GO SUB 7000: LET x=155: GO SUB 7000: LET x=215: GO SUB 7000 +5500 GO SUB 3000: GO SUB 1000 +5510 IF pz<=0 THEN GO TO 6000: END IF +5520 GO SUB 2000 +5530 GO TO 5500 +6000 IF ABS rl>rt OR pt>tp OR pt<0 OR as0>80 THEN GO TO 6030: END IF +6010 IF ABS px>wr OR ABS py>1000 THEN GO TO 6060: END IF +6020 CLS : PRINT "CONGRATULATIONS ON A SUCCESSFUL LANDING!": GO TO 6100 +6030 FOR n=0 TO 20 STEP .5: PLOT 127,130+16: DRAW 120-INT (RND*240),45-INT (RND*90): BEEP .005,20-n: NEXT n +6040 PAUSE 50 +6050 CLS : PRINT "A CRASH LIKE THAT HAS WRECKED THE AIRCRAFT!": GO TO 6100 +6060 CLS : PRINT "YOU LANDED OFF THE RUNWAY" +6070 IF as0<40 THEN PRINT "FORTUNATELY YOU WEREN'T GOING FAST ENOUGH TO DO MUCH DAMAGE": GO TO 6100: END IF +6080 IF as0<80 THEN PRINT "AT THAT SPEED YOU GOT AWAY WITH LIGHT DAMAGE AND A FEW BRUISES": GO TO 6100: END IF +6090 PRINT "MISSING THE RUNWAY AT THAT SPEED HAS LEFT NO SURVIVORS!" +6100 PRINT : PRINT : PRINT "FINAL FLIGHT DETAILS:": PRINT +6110 PRINT "AIRSPEED= ";INT as0;" M/S": PRINT "DISTANCE= ";INT (SQR (py*py+px*px)): PRINT "PITCH = ";pt +6120 PRINT "ROLL = ";rl: PRINT "RPM = ";INT (10*tc)/10;" X 1000" +6130 PRINT "DRIFT = ";INT ABS px;" MTRS": PRINT "BEARING = ";ad;" DEGREES" +6140 PRINT : PRINT "DO YOU WANT ANOTHER GO (Y/N)?" +6150 LET a$=INKEY$: IF a$<>"y" AND a$<>"n" THEN GO TO 6150: END IF +6160 IF a$="n" THEN GO TO 6900: END IF +6170 CLS : GO TO 1 +6900 STOP +7000 FOR kk=0 TO 2*PI STEP PI/5: PLOT x+17*SIN kk,16+y+17*COS kk: DRAW 2*SIN kk,2*COS kk: NEXT kk: RETURN +8000 IF gc<>0 THEN PLOT 127,174+16: DRAW OVER 1;ox,oy: END IF +8010 LET ox=16*SIN (rb*(PI/180)): LET oy=-(16*ABS COS (rb*(PI/180))) +8020 PLOT 127,174+16: DRAW OVER 1;ox,oy +8025 LET wb=ad: IF ad>180 THEN LET wb=wb-360: END IF +8026 IF rb>180 THEN LET wb=wb+360-rb: GO TO 8040: END IF +8030 LET wb=wb-rb +8040 IF rw=1 THEN PLOT OVER 1;rdx,175+16-rdy: END IF +8050 LET rw=0: IF ABS wb>57 THEN RETURN: END IF +8060 LET rdx=x3+INT (((x4-x3)/2)-SIN (wb*(PI/180))*(x4-x3)*.6) +8070 LET rdy=y3+((y4-y3)*((rdx-x3)/(x4-x3))+2) +8080 IF rdy<2 OR rdy>90 OR rdx<11 OR rdx>244 THEN RETURN: END IF +8090 LET rw=1: PLOT OVER 1;rdx,175+16-rdy +8100 RETURN diff --git a/examples/sd81/maskedsprites_sd81.bas b/examples/sd81/maskedsprites_sd81.bas new file mode 100644 index 000000000..8c1e3a910 --- /dev/null +++ b/examples/sd81/maskedsprites_sd81.bas @@ -0,0 +1,810 @@ +' ---------------------------------------------------------------- +' This file is released under the MIT License +' +' Copyright (C) 2026 Conrado Badenas +' +' Example for +' Print Masked (AND+OR) Sprites, version 2026.04.05 +' +' Version zx81sd: WaitForNewFrame usaba EI+HALT esperando la interrupcion +' de 50Hz del Spectrum (via ROM, contador FRAMES en 23672). En zx81sd las +' interrupciones estan permanentemente deshabilitadas (el vector $0038 es +' solo una trampa DI;HALT, no un manejador real) — ese HALT no despertaria +' nunca. Sustituido por VSYNC_TICK (runtime/vsync.asm), que sondea el +' contador de pulsos VSYNC del SD81 Booster por puerto ($AFh) sin usar +' interrupciones, igual que hace ya PAUSE. Resto del fichero sin cambios. +' ---------------------------------------------------------------- + +' DEFINEs to control the library cb/maskedsprites.bas + #define NumberofMaskedSprites 10 ' Try 50 here, with #define USE_MSFS + #define MaskedSprites_USE_STACK_TRANSFER +'#include "maskedsprites.bas" + #include + +' DEFINEs to control this example program + #define NUMWAITS -1 + #define BORDER_WAIT 0 + #define USE_MSFS + #define INTS di + +#include + +CONST NUMSPRITES as UByte = NumberofMaskedSprites + +dim i as Byte +dim memoryPaging,numberofscrolls,loops,warnings,modulus as UByte +dim x0(0 to NUMSPRITES-1) as UByte +dim y0(0 to NUMSPRITES-1) as UByte +dim x1(0 to NUMSPRITES-1) as UByte +dim y1(0 to NUMSPRITES-1) as UByte +dim back(0 to NUMSPRITES-1) as UInteger +#ifdef USE_MSFS + dim regs(0 to NUMSPRITES-1) as UInteger + dim regHero0,regFoe00,regFoe20,regFoe21,regFoe22,regFoe23 as UInteger +#endif +dim temp as Integer +dim pointx1,pointx2,pointy1,pointy2 as Integer 'for subtractions + +ASM + INTS +END ASM + +for i=0 to NUMSPRITES-1 + y0(i)=255:y1(i)=255:x1(i)=randomLimit(240) +next i:x1(0)=120 + +'DIM Rosquilla_Sprite(31) AS UByte => { _ +' $00,$07,$18,$20,$23,$44,$48,$48, _ +' $48,$48,$44,$23,$20,$18,$07,$00, _ +' $00,$E0,$18,$04,$C4,$22,$12,$12, _ +' $12,$12,$22,$C4,$04,$18,$E0,$00 _ +' } +DIM Rosquilla0(31) AS UByte => { _ + $00,$07,$19,$21,$23,$44,$48,$48, _ + $48,$58,$7C,$3B,$20,$18,$07,$00, _ + $00,$E0,$98,$84,$C4,$22,$12,$12, _ + $12,$1A,$3E,$DC,$04,$18,$E0,$00 _ + } +DIM Rosquilla1(31) AS UByte => { _ + $00,$07,$1C,$2E,$2F,$44,$48,$48, _ + $48,$48,$44,$2F,$2E,$1C,$07,$00, _ + $00,$E0,$18,$04,$C4,$22,$12,$1E, _ + $1E,$12,$22,$C4,$04,$18,$E0,$00 _ + } +DIM Rosquilla2(31) AS UByte => { _ + $00,$07,$18,$20,$3B,$7C,$58,$48, _ + $48,$48,$44,$23,$21,$19,$07,$00, _ + $00,$E0,$18,$04,$DC,$3E,$1A,$12, _ + $12,$12,$22,$C4,$84,$98,$E0,$00 _ + } +DIM Rosquilla3(31) AS UByte => { _ + $00,$07,$18,$20,$23,$44,$48,$78, _ + $78,$48,$44,$23,$20,$18,$07,$00, _ + $00,$E0,$38,$74,$F4,$22,$12,$12, _ + $12,$12,$22,$F4,$74,$38,$E0,$00 _ + } +memoryPaging = CheckMemoryPaging() +if memoryPaging then SetVisibleScreen(5) '128K +paper 1:ink 7:border 0:cls + +for i=0 to 126 + pointx1=randomLimit(255):pointy1=randomLimit(191) + pointx2=randomLimit(255):pointy2=randomLimit(191) + plot pointx1,pointy1:draw pointx2-pointx1,pointy2-pointy1 +next i + +#ifdef USE_MSFS + print "Init MSFS at ";InitMaskedSpritesFileSystem() + print "Free Blocks in MSFS = ";GetNumberofFreeBlocksInMSFS() + regHero0 = RegisterSpriteImageInMSFS(@hero0):if regHero0=0 then STOP + print "regHero0 = ";regHero0 + regFoe00 = RegisterSpriteImageInMSFS(@foe00):if regFoe00=0 then STOP + print "regFoe00 = ";regFoe00 + regFoe20 = RegisterSpriteGraphAndMaskInMSFS(@Rosquilla0(0),@Rosquilla_Sprite_Mask):if regFoe20=0 then STOP + print "regFoe20 = ";regFoe20 + regFoe21 = RegisterSpriteGraphAndMaskInMSFS(@Rosquilla1(0),@Rosquilla_Sprite_Mask):if regFoe21=0 then STOP + print "regFoe21 = ";regFoe21 + regFoe22 = RegisterSpriteGraphAndMaskInMSFS(@Rosquilla2(0),@Rosquilla_Sprite_Mask):if regFoe22=0 then STOP + print "regFoe22 = ";regFoe22 + regFoe23 = RegisterSpriteGraphAndMaskInMSFS(@Rosquilla3(0),@Rosquilla_Sprite_Mask):if regFoe23=0 then STOP + print "regFoe23 = ";regFoe23 + + for i=0 to NUMSPRITES-1: modulus = i mod 2 + if i=0 then + regs(0)=regHero0 + elseif modulus=1 then + regs(i)=regFoe00 + else ' modulus=0 with i>0 + regs(i)=regFoe20 + end if + next i +' pause 0 +#endif + +for i=0 to NUMSPRITES-1 + back(i)=MaskedSpritesBackground(i) +' print back(i) +next i +' print:pause 0 + +if memoryPaging then '128K + dim back0(0 to NUMSPRITES-1) as UInteger + ChangeMaskedSpritesBackgroundSet + for i=0 to NUMSPRITES-1 + back0(i)=MaskedSpritesBackground(i) +' print back0(i) + next i + ChangeMaskedSpritesBackgroundSet +' print:print MaskedSpritesFileSystemStart:pause 0 + + CopyScreen5ToScreen7() +' print at 11,8;"This is Screen5" + SetDrawingScreen7() 'Bank7 is set at $c000 +' print at 13,8;"This is Screen7" + SetDrawingScreen5() 'Bank7 is still set at $c000 +' print at 15,8;"This is Screen5" +end if + +' We start with VisibleScreen = DrawingScreen = 5 +numberofscrolls=0 +warnings=0 +loops=0 +WaitForNewFrame(0) +do + + ' 128K + if memoryPaging then + ' The brand-new VisibleScreen is ready for the ULA to show it on TV + ' But we wait to the beginning of a frame to avoid tearing + ' (unless wanted time-to-wait has already been spent) + out 254,BORDER_WAIT:WaitForNewFrame(NUMWAITS):out 254,0 +'GetInterruptStatusInBorder() + ToggleVisibleScreen() + ' Now we can modify DrawingScreen because it is not Visible + + ' We restore background in x0,y0 if it is saved in MaskedSpritesBackground + if MaskedSpritesBackgroundSet then + for i=NUMSPRITES-1 to 0 step -1 + if y0(i)<>255 then RestoreBackground(x0(i),y0(i),back0(i)) + next i + else + for i=NUMSPRITES-1 to 0 step -1 + if y0(i)<>255 then RestoreBackground(x0(i),y0(i),back(i)) + next i + end if + end if + + ' After restoring background in a Spectrum 128K, you can use CPU to do something else: + ' + ' 0. Change/scroll background (ONLY NOW!!!) + ' 1. Check collisions + ' 2. Resolve collisions + ' 3. Change images of sprites for: + ' a. Animation of regular motion + ' b. Explosions and other extraordinary events + ' 4. Upgrade the score + ' 5. Play sound/music + ' ... + ' + ' You have plenty of time to do whatever you want in a Spectrum 128K + + ' 0. Change/scroll background (ONLY NOW!!!) + if memoryPaging=1 then '128K + if (in($7ffe) bAND 1)=0 then + if numberofscrolls=1 then + Scroll2() + else + Scroll1() + numberofscrolls=1 + end if + else + if numberofscrolls=1 then + Scroll1() + end if + numberofscrolls=0 + end if + end if + +#ifdef USE_MSFS + ' 3. Change images of sprites for: a. Animation of regular motion + for i=2 to NUMSPRITES-1 step 2: modulus = (i+loops) mod 4 + if modulus=0 then + regs(i)=regFoe20 + elseif modulus=1 then + regs(i)=regFoe21 + elseif modulus=2 then + regs(i)=regFoe22 + else + regs(i)=regFoe23 + end if + next i +#endif + + ' We update x0,y0 and compute new x1,y1 + for i=1 to NUMSPRITES-1 + temp=x1(i): x0(i)=temp + temp=temp-1 + if temp<0 then temp=240 + x1(i)=temp + temp=y1(i): y0(i)=temp + if temp=255 then temp=randomLimit(176) + if randomLimit(3)=0 then temp=temp+randomLimit(2)-1 + if temp<0 then temp=0 + if temp>176 then temp=176 + y1(i)=temp + next i + + 'Special code for the hero + temp=x1(0): x0(0)=temp + temp=temp+1 -2*(in($dffe) bAND 1) +2*((in($dffe)>>1) bAND 1) '-noP+noO + if temp<0 then temp=240 + if temp>240 then temp=0 + x1(0)=temp + temp=y1(0): y0(0)=temp + if temp=255 then temp=randomLimit(176) + temp=temp -(in($fdfe) bAND 1) +(in($fbfe) bAND 1) '-noA+noQ + if temp<0 then temp=0 + if temp>176 then temp=176 + y1(0)=temp + + ' 48K We restore background in x0,y0 if it is saved in MaskedSpritesBackground + if not memoryPaging then + for i=NUMSPRITES-1 to 0 step -1 + if y0(i)<>255 then RestoreBackground(x0(i),y0(i),back(i)) + next i + end if + +#ifdef USE_MSFS + ' Print WARNING (if not scrolling) twice if MSFS has run out of free blocks + if not numberofscrolls and warnings<2 and not GetNumberofFreeBlocksInMSFS() then + print at 23,0;"WARNING! MSFS has NO free blocks"; + warnings=warnings+1 + end if +#endif + + ' After restoring background in a Spectrum 48K, you should do NOTHING + ' ... + ' You have ABSOLUTELY NO TIME to do anything in a Spectrum 48K + + ' We save background in MaskedSpritesBackground and draw sprites in x,y... +#ifdef USE_MSFS + ' ...using SaveBackgroundAndDrawSpriteRegisteredInMSFS(,,,) + if MaskedSpritesBackgroundSet then + for i=0 to NUMSPRITES-1 + SaveBackgroundAndDrawSpriteRegisteredInMSFS(x1(i),y1(i),back0(i),regs(i)) + next i + else + for i=0 to NUMSPRITES-1 + SaveBackgroundAndDrawSpriteRegisteredInMSFS(x1(i),y1(i),back(i),regs(i)) + next i + end if +#else + ' ...using SaveBackgroundAndDrawSprite(,,,) + if MaskedSpritesBackgroundSet then + SaveBackgroundAndDrawSprite(x1(0),y1(0),back0(0),@hero0) + for i=1 to NUMSPRITES-1 + SaveBackgroundAndDrawSprite(x1(i),y1(i),back0(i),@foe00) + next i + else + SaveBackgroundAndDrawSprite(x1(0),y1(0),back(0),@hero0) + for i=1 to NUMSPRITES-1 + SaveBackgroundAndDrawSprite(x1(i),y1(i),back(i),@foe00) + next i + end if +#endif + + ' After drawing sprites, you can use CPU to do something else: + ' + ' 1. Check collisions + ' 2. Resolve collisions + ' 3. Change images of sprites for: + ' a. Animation of regular motion + ' b. Explosions and other extraordinary events + ' 4. Upgrade the score + ' 5. Play sound/music + ' ... + ' + ' You have plenty of time to do whatever you want in ANY Spectrum + + ' We change the Set of Backgrounds (128K) for next iteration + if memoryPaging then ChangeMaskedSpritesBackgroundSet() + + ' We have finished drawing + if memoryPaging then + ' We change DrawingScreen (128K) for next iteration + ToggleDrawingScreen() + else + ' ULA shows DrawingScreen (48K) on TV for some frames before going on + out 254,BORDER_WAIT:WaitForNewFrame(NUMWAITS):out 254,0 + end if +'GetInterruptStatusInBorder() + ' Now, VisibleScreen = DrawingScreen, and + ' we could see anything we draw whilst it is drawn + loops=loops+1 +loop + +' SUB/FUNCTIONs that are not used, are "used" here to check compilation is OK + SetBankPreservingINTs(0) +print GetBankPreservingRegs() +print CheckMemoryPaging() + SetVisibleScreen(0) +print GetVisibleScreen() + ToggleVisibleScreen() + CopyScreen5ToScreen7() + CopyScreen7ToScreen5() + SetDrawingScreen5() +print SetDrawingScreen7() + ToggleDrawingScreen() +print ChangeMaskedSpritesBackgroundSet() + SaveBackgroundAndDrawSprite(0,0,0,0) + RestoreBackground(0,0,0) +print InitMaskedSpritesFileSystem() +print GetNumberofFreeBlocksInMSFS() +print RegisterSpriteImageInMSFS(0) +print RegisterSpriteGraphAndMaskInMSFS(0,0) + SaveBackgroundAndDrawSpriteRegisteredInMSFS(0,0,0,0) +GetInterruptStatusInBorder() +Scroll() +Scroll1() +Scroll2() +' Delete/Comment all these "uses" when used, or compilation checking is not needed + +hero0: +ASM + defb %11000000,%00000000,%00001111,%00000000;1 + defb %10000000,%00011111,%00000000,%11100000;2 + defb %00000000,%00100000,%00000000,%00111110;3 + defb %00000000,%01001110,%00000000,%11111000;4 + defb %00000000,%01011001,%00000001,%00001100;5 + defb %00000000,%01011010,%00000000,%00010100;6 + defb %00000000,%00000011,%00000000,%11100010;7 + defb %00000000,%01111100,%00000000,%00100010;8 + defb %00000000,%01111100,%00000000,%00100010;8 + defb %00000000,%00000011,%00000000,%11100010;7 + defb %00000000,%01011010,%00000000,%00010100;6 + defb %00000000,%01011001,%00000001,%00001100;5 + defb %00000000,%01001110,%00000000,%11111000;4 + defb %00000000,%00100000,%00000000,%00111110;3 + defb %10000000,%00011111,%00000000,%11100000;2 + defb %11000000,%00000000,%00001111,%00000000;1 +END ASM + +foe00: +ASM + defb %00000011,%00000000,%11000000,%00000000;1 + defb %00000011,%01111000,%11000000,%00011110;2 + defb %00000011,%01000000,%11000000,%00010000;3 + defb %00000001,%01010000,%10000000,%00010100;4 + defb %00000000,%01001000,%00000000,%00100010;5 + defb %00000000,%00000010,%00000000,%01110000;6 + defb %11100000,%00000111,%00000111,%11100000;7 + defb %11110000,%00000010,%00001111,%01000000;8 + defb %11110000,%00000010,%00001111,%01000000;8 + defb %11100000,%00000111,%00000111,%11100000;7 + defb %00000000,%00001110,%00000000,%01000000;6' + defb %00000000,%01110100,%00000000,%00011110;5' + defb %00000001,%01000000,%10000000,%00010000;4' + defb %00000011,%01010000,%11000000,%00010100;3' + defb %00000011,%01001000,%11000000,%00010010;2' + defb %00000011,%00000000,%11000000,%00000000;1 +END ASM + +Rosquilla_Sprite_Mask: +ASM + db $FF,$F8,$E0,$C0,$C0,$83,$87,$87 + db $87,$87,$83,$C0,$C0,$E0,$F8,$FF + db $FF,$1F,$07,$03,$03,$C1,$E1,$E1 + db $E1,$E1,$C1,$03,$03,$07,$1F,$FF +END ASM + + +' ---------------------------------------------------------------- +' READ_IFF2 is a MACRO that reads correctly the IFF2 flip-flop, +' avoiding a "bug" reported in the Z80 User Manual, +' which Pedro Picapiedra aka ProgramadorHedonista kindly pointed +' me to: "If an interrupt occurs during execution of this +' instruction [LD A,I or LD A,R], the Parity flag contains a 0." +' ---------------------------------------------------------------- +#define READ_IFF2 \ + ld a,i ; IFF2=0/1=DI/EI is saved in PF=0/1=Odd/Even \ + jp pe,1f ; if PF=Even=1, it is sure that IFF2=1=EI \ + ld a,i ; read IFF2 again to ensure that IFF2=0=DI \ +1: + + +' ---------------------------------------------------------------- +' Wait for New Frame — version zx81sd +' +' El original usaba EI+HALT esperando la interrupcion IM1 de 50Hz de la +' ROM del Spectrum, y comparaba contra el contador FRAMES de la ROM en +' la direccion absoluta 23672. En zx81sd no hay interrupciones (todo el +' runtime corre con DI permanente; el vector $0038 es una trampa +' DI;HALT, no un manejador real) ni existe esa direccion de sysvar. +' Sustituido por VSYNC_TICK (runtime/vsync.asm): sondea por puerto el +' contador de pulsos VSYNC del SD81 Booster (hardware, sin interrupciones) +' e incrementa nuestro propio sysvar FRAMES. Se llama una vez por cada +' frame que haya que esperar, en vez de un unico HALT seguido de un +' bucle que confiaba en que la interrupcion siguiera incrementando FRAMES +' en segundo plano mientras el bucle solo comprobaba sin esperar. +' Parameters: +' Byte: if =0, you want just one frame wait +' if >0, minimum number of frames spent since last wait +' if <0, return without waiting +' ---------------------------------------------------------------- +SUB FASTCALL WaitForNewFrame(minimumNumberofFramesToWaitSinceLastWait AS Byte) +ASM + push namespace core + PROC + LOCAL wait,enough,temp + + and a + ret m ; return if minimumNumberofFramesToWaitSinceLastWait < 0 + ld hl,temp + ld de, FRAMES + ld c,a ; C = minimumNumberofFramesToWaitSinceLastWait + + call VSYNC_TICK ; garantiza al menos un frame esperado (sustituye EI+HALT) + +wait: + ld a,(de) + sub (hl) ; A = number of frames since last RET + cp c ; is A >= C? Yes: NoCarry. No: Carry + jr nc,enough + call VSYNC_TICK ; espera otro frame (FRAMES lo incrementa VSYNC_TICK) + jr wait +enough: + ld a,(de) + ld (hl),a + ret +temp: + defb 0 + ENDP + pop namespace +END ASM +END SUB +#require "vsync.asm" + + +' ---------------------------------------------------------------- +' Get Interrupt Status in Border +' This SUB uses a hack to ensure a good reading of the IFF2 flip-flop. +' Hack was found thanks to Pedro Picapiedra aka ProgramadorHedonista. +' Border results: 2 Red if Interrupts are Disabled IFF2=0 +' 4 Green if Interrupts are Enabled IFF2=1 +' +' Version zx81sd: se deja sin cambios (no se llama nunca en el bucle +' principal, solo aparece comentada mas abajo; se mantiene unicamente +' para que la comprobacion de compilacion del final del fichero no falle +' por "funcion no usada"). En zx81sd siempre mostraria rojo, ya que las +' interrupciones estan permanentemente deshabilitadas. +' ---------------------------------------------------------------- +SUB FASTCALL GetInterruptStatusInBorder() +ASM + READ_IFF2 + push af + pop bc + ld a,c + and 4 ; A=0/4 iff IFF2=0/1 + rra ; A=0/2 iff IFF2=0/1 + add a,2 ; A=2/4 iff IFF2=0/1 + out (254),a +END ASM +END SUB + + +SUB FASTCALL Scroll() +ASM + PROC + LOCAL loop + ld hl,(.core.SCREEN_ADDR) + ld de,31 + ld b,192 +loop: +; Here, HL = screen address for first column + add hl,de; column 32 + xor a + rl (hl) ; 32 + dec l + rl (hl) ; 31 + dec l + rl (hl) ; 30 + dec l + rl (hl) ; 29 + dec l + rl (hl) ; 28 + dec l + rl (hl) ; 27 + dec l + rl (hl) ; 26 + dec l + rl (hl) ; 25 + dec l + rl (hl) ; 24 + dec l + rl (hl) ; 23 + dec l + rl (hl) ; 22 + dec l + rl (hl) ; 21 + dec l + rl (hl) ; 20 + dec l + rl (hl) ; 19 + dec l + rl (hl) ; 18 + dec l + rl (hl) ; 17 + dec l + rl (hl) ; 16 + dec l + rl (hl) ; 15 + dec l + rl (hl) ; 14 + dec l + rl (hl) ; 13 + dec l + rl (hl) ; 12 + dec l + rl (hl) ; 11 + dec l + rl (hl) ; 10 + dec l + rl (hl) ; 09 + dec l + rl (hl) ; 08 + dec l + rl (hl) ; 07 + dec l + rl (hl) ; 06 + dec l + rl (hl) ; 05 + dec l + rl (hl) ; 04 + dec l + rl (hl) ; 03 + dec l + rl (hl) ; 02 + dec l + rl (hl) ; 01 + rla ; save CF in A + add hl,de; column 32 + or (hl) + ld (hl),a + inc hl ; first column + djnz loop + ENDP +END ASM +END SUB + + +' ---------------------------------------------------------------- +' NEXT_ROW is a MACRO of ASM code, based on code from +' https://zonadepruebas.com/viewtopic.php?f=15&t=8372&start=40#p81507 +' and found by Joaquin Ferrero +' ---------------------------------------------------------------- +#define NEXT_ROW \ + sub 224 ; 7 A was L, now A = L + 32 (SUB 224 is similar to +32) \ + ; CF = 0/1 iff E >=/< 224 iff a third is/isn't crossed \ + ld l,a ; 4 \ + sbc a,a ; 4 A = 0/255 \ + and 248 ; 7 A = 0/248 (248 = -8) \ + add a,h ; 4 A = H/H-8 iff a third is/isn't crossed \ + ld h,a ; 4 += 30 Ts + + +' ---------------------------------------------------------------- +' Scroll 1 pixel up +' ---------------------------------------------------------------- +SUB FASTCALL Scroll1() +ASM + PROC + LOCAL loop1,loop2,exit + push ix + xor a + ld hl,(.core.SCREEN_ADDR); HL = $4000 or $c000 + ld de,buffer0 + call ldi32 ; scanline0 + ; DEC HL is not needed because row = 0 <> 7, 15, 23 + ld ixh,24 ; 24 rows (rows 0-23) +loop1: + ld l,a ; A = low byte of screen addresses for this row +; HL = scanline0 for this row + ld d,h + ld e,l ; DE = scanline0 of this row + inc h ; HL = scanline1 of this row + ld ixl,7 ; first 7 scanlines of one row +loop2: +; HL,DE = origin,destination screen address + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi ; BC-=32 DE+=32 HL+=32 (if E,L was 224, then D,H changes) + dec hl ; 6 in case H changes (because L was 224: rows 7,15,23) + ld l,a ; +4 = 10 Ts < 21 Ts (PUSH+POP) + ld d,h + ld e,l ; scanline_N+0 of this row + inc h ; scanline_N+1 of this row + dec ixl + jp nz,loop2 + dec ixh + jr z,exit ; this JR Z here (instead of an JP NZ later) adds +7 Ts/row = 161 Ts for 23 rows +; last scanline of first 23 rows: DE is OK, HL must change + NEXT_ROW + ld a,l + call ldi32 ; 539 Ts needed for each row except last row + dec hl ; with JR Z,EXIT before, we save 539-161 = 378 Ts for last row + jp loop1 +exit: + ld hl,buffer0 + call ldi32 ; DE = scanline7 of last row + pop ix + ENDP +END ASM +END SUB + + +' ---------------------------------------------------------------- +' Scroll 2 pixels up +' ---------------------------------------------------------------- +SUB FASTCALL Scroll2() +ASM + PROC + LOCAL loop1,loop2,exit + push ix + xor a + ld hl,(.core.SCREEN_ADDR); HL = $4000 or $c000 + ld de,buffer0 + call ldi32 ; scanline0 + ; DEC HL is not needed because row = 0 <> 7, 15, 23 + ld l,a + inc h + call ldi32 ; scanline1 + ; DEC HL is not needed because row = 0 <> 7, 15, 23 + ld ixh,24 ; 24 rows (rows 0-23) +loop1: + ld l,a ; A = low byte of screen addresses for this row +; HL = scanline1 for this row + ld d,h + ld e,l + dec d ; DE = scanline0 of this row + inc h ; HL = scanline2 of this row + ld ixl,6 ; first 6 scanlines of one row +loop2: +; HL,DE = origin,destination screen address + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi ; BC-=32 DE+=32 HL+=32 (if E,L was 224, then D,H changes) + dec hl ; 6 in case H changes (because L was 224: rows 7,15,23) + ld l,a ; +4 = 10 Ts < 21 Ts (PUSH+POP) + ld d,h + ld e,l + dec d ; DE = scanline_N+0 of this row + inc h ; HL = scanline_N+2 of this row + dec ixl + jp nz,loop2 + dec ixh + jr z,exit ; this JR Z here (instead of an JP NZ later) adds +7 Ts/row = 161 Ts for 23 rows +; last scanline of first 23 rows: DE is OK, HL must change + NEXT_ROW + ld c,h ; C=H is larger than 32, so B does not change when BC-=32 + ld b,e ; DE = scanline6 of this row SAVED in B + ld a,l ; HL = scanline0' of next row SAVED in A + call ldi32 ; 539 Ts needed for each row except last row + dec hl + ld l,a + dec de ; LD C,H + LD B,E + LD A,L + DEC HL + LD L,A + DEC DE + LD E,B + ld e,b ; = 4+4+4+6+4+6+4 = 32 Ts < 42 Ts 2*(PUSH+POP) + inc d ; DE = scanline7 of this row + inc h ; HL = scanline1' of next row + call ldi32 ; 539 Ts needed for each row except last row + dec hl ; with JR Z,EXIT before, we save 2*539-161 = 917 Ts for last row + jp loop1 +exit: + ld hl,buffer0 + call ldi32 ; DE = scanline6 of last row + dec de + ld e,a + inc d ; DE = scanline7 of last row + call ldi32 + pop ix + ret + ENDP + +buffer0: + defs 32 + +buffer1: + defs 32 + +ldi32: + ldi ; 32 LDI = 32*16 = 512 Ts + ldi ; CALL + RET = 17+10 = 27 Ts + ldi ; call ldi32 (+ret) = 512+27 = 539 Ts + ldi + ldi ; LD BC,32 + LDIR = 10 + 21*31+16 = 677 Ts + ldi + ldi ; 677 - 539 = 138 Ts + ldi + ldi ; then, "call ldi32 (+ret)" is 138 Ts faster than "ld bc,32 + ldir" + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi + ldi ; BC-=32 DE+=32 HL+=32 (if E,L was >=224, then D,H changes) +END ASM +END SUB diff --git a/examples/sd81/pong.bas b/examples/sd81/pong.bas new file mode 100644 index 000000000..cc11a2888 --- /dev/null +++ b/examples/sd81/pong.bas @@ -0,0 +1,237 @@ + +10 REM Pong! Game + +14 REM Digit data + REM 128 => "\ " + REM 131 => "\''" + REM 138 => "\: " + REM 133 => "\ :" + REM 139 => "\:'" + REM 141 => "\.:" + REM 140 => "\.." + REM 135 => "\':" + REM 142 => "\:." + +15 DIM numbers(9, 2, 1) as Ubyte = { _ + {{139, 135}, {138, 133}, {142, 141}}, _ ' Number 0 + {{128, 133}, {128, 133}, {128, 133}}, _ ' Number 1 + {{131, 135}, {139, 131}, {142, 140}}, _ ' Number 2 + {{131, 135}, {131, 135}, {140, 141}}, _ ' Number 3 + {{138, 133}, {131, 135}, {128, 133}}, _ ' Number 4 + {{139, 131}, {131, 135}, {140, 141}}, _ ' Number 5 + {{139, 131}, {139, 135}, {142, 141}}, _ ' Number 6 + {{131, 135}, {128, 133}, {128, 133}}, _ ' Number 7 + {{139, 135}, {139, 135}, {142, 141}}, _ ' Number 8 + {{139, 135}, {131, 135}, {140, 141}} _ ' Number 9 + } +20 BORDER 0: PAPER 0: INK 7: BRIGHT 1: OVER 1: CLS + PRINT AT 10, 5; BOLD 1; "PRESS Q or 4 TO MOVE UP" + PRINT AT 11, 4; BOLD 1; "PRESS A or 3 TO MOVE DOWN": PAUSE 0 + DIM h, xx, yy, p, oldX, oldY As Byte + DIM x, y, dx, dy as Fixed + DIM px, py, NUM as Byte + DIM i, delay as Uinteger + DIM diff as Float = 0.5 : REM difficulty level + CONST minX As Byte = 0 + CONST minY As Byte = 0 + CONST maxX As Byte = 61 + CONST maxY As Byte = 47 + CONST comp As Byte = 0: REM Computer player is left + CONST user As Byte = 1: REM User player is left + CONST startX as Byte = maxX / 2: REM Ball initial X coord + CONST startY as Byte = maxY / 2: REM Ball initial Y coord + CONST dFast as UInteger = 400 + CONST dSlow as UInteger = 1000 + CONST diffRate As Float = 1.125: REM difficulty rate + CONST pointsToWin As Byte = 10: REM Points to Win + + DIM score(1) As Byte: REM Scores + +30 CLS: LET xx = 31: FOR yy = minY TO maxY STEP 2: GOSUB 2000: NEXT yy +40 DIM coords(1, 1) As Byte: REM Player 0 (Left) and 1 (Right) coordinates +50 LET h = 5: REM players height (in "points"). A "point" is 4 pixels +60 LET x = startX: LET y = startY: REM Screen resolution is 64 +70 LET coords(0, 0) = minX: LET coords(1, 0) = maxX +80 LET coords(0, 1) = minY + (maxY - minY) / 2: LET coords(1, 1) = coords(0, 1) +90 LET dx = 1: LET dy = 1: LET delay = dSlow: REM Ball speed +91 LET score(0) = 0: LET score(1) = 0: REM Initializes scores + +93 FOR p = 0 TO 1: GOSUB 1000: NEXT p: REM Draw players +94 GOSUB 3000: REM Print scores + +95 REM Draws Ball. Begin of GAME LOOP +96 LET xx = x: LET yy = y: LET oldX = x: LET oldY = y: GOSUB 2000 +100 LET x = x + dx +110 IF x < minX THEN + LET x = minX: LET dx = -dx: BEEP 0.02,20 + LET py = coords(0, 1) + IF py > y OR py + h <= y THEN + REM Player 2 wins 1 Point + LET score(1) = score(1) + 1 + GOSUB 3000 + LET y = startY + LET x = coords(1, 0) + LET dx = -dx + LET delay = dSlow + IF comp <> 1 THEN + LET diff = diff / diffRate + ELSE + LET diff = diff * diffRate + END IF + ELSE + IF y = py OR y = py + h - 1 THEN + delay = dFast + ELSE + delay = dSlow + END IF + END IF + ELSEIF x > maxX THEN + LET x = maxX: LET dx = -dx: BEEP 0.02,20 + LET py = coords(1, 1) + IF py > y OR py + h <= y THEN + REM Player 1 wins 1 Point + LET score(0) = score(0) + 1 + GOSUB 3000 + LET y = startY + LET x = coords(0, 0) + LET dx = -dx + LET delay = dSlow + IF comp <> 0 THEN + LET diff = diff / diffRate + ELSE + LET diff = diff * diffRate + END IF + ELSE + IF y = py OR y = py + h - 1 THEN + delay = dFast + ELSE + delay = dSlow + END IF + END IF + END IF + +120 LET y = y + dy +130 IF y < minY THEN + LET y = minY: BEEP 0.02,10: LET dy = -dy + ELSEIF y > maxY THEN + LET y = maxY: BEEP 0.02,10: LET dy = -dy + END IF + +140 REM Checks if computer must move +150 LET px = coords(comp, 0): LET py = coords(comp, 1) +160 IF py > y AND RND > diff THEN REM Must go up + LET p = comp + GOSUB 1500: REM Updates player padel (up) + ELSEIF py + h - 1 < y AND RND > diff THEN + LET p = comp + GOSUB 1600: REM Updates player padel (down) + END IF + +200 REM Checks if Player moves ("4", "3") +210 LET px = coords(user, 0): LET py = coords(user, 1) +220 if py > minY AND (INKEY$ = "4" OR INKEY$ = "q") THEN REM Must go up + LET p = user + GOSUB 1500: REM Updates player padel (up) + ELSEIF py + h < maxY AND (INKEY$ = "3" OR INKEY$ = "a") THEN REM Must go down + LET p = user + GOSUB 1600: REM Updates player padel (down) + END IF + +250 FOR i = 0 TO delay: NEXT i + +500 LET xx = oldX: LET yy = oldY + ASM + call .core.VSYNC_TICK ; garantiza al menos un frame esperado (sustituye EI+HALT) + ; halt ; Avoids screen flickering + END ASM + GOSUB 2000: REM erases the ball + GOTO 95: REM Game loop + +0999 END +1000 REM Draws player p at p(p, 0), p(p, 1) +1010 LET xx = coords(p, 0) +1020 LET yy = coords(p, 1) +1030 FOR i = 1 TO h: GOSUB 2000: LET yy = yy + 1: NEXT i +1040 RETURN + +1500 REM Moves up the padel for user p (p = 0 => Left, p = 1 = Right) +1510 LET xx = px +1520 FOR i = 0 TO 1: IF py <= minY THEN EXIT FOR: END IF +1530 LET py = py - 1 +1540 LET yy = py: GOSUB 2000: REM Draws padel top +1550 LET yy = py + h: GOSUB 2000: REM Erases padel bottom +1560 NEXT i +1570 LET coords(p, 1) = py: RETURN + +1600 REM Moves down the padel for user p (p = 0 => Left, p = 1 = Right) +1610 LET xx = px: LET yy = py +1620 FOR i = 0 TO 1: IF py + h > maxY THEN EXIT FOR: END IF +1630 LET yy = py: GOSUB 2000: REM Erases padel top +1640 LET yy = py + h: GOSUB 2000: REM Draws padel bottom +1650 LET py = py + 1 +1660 NEXT i +1670 LET coords(p, 1) = py: RETURN + +2000 REM Draws a "Point" at xx,yy +2010 LET cx1 = xx bAND 1: LET cy1 = yy bAND 1 +2020 LET cx2 = xx SHR 1: LET cy2 = yy SHR 1 +2040 IF cx1 THEN + IF cy1 THEN + LET c$ = "\ ." + ELSE + LET c$ = "\ '" + END IF + ELSE + IF cy1 THEN + LET c$ = "\. " + ELSE + LET c$ = "\' " + END IF + END IF + +2050 PRINT AT cy2, cx2; c$; +2060 RETURN + +3000 REM Prints SCORES +3010 FOR player = 0 TO 1 +3020 LET atY = minY + 1: LET atX = minX + 4 + player * (maxX - minX) / 4: LET sc = score(player) +3030 IF sc < 10 THEN REM erases left digit + FOR i = 0 TO 2: PRINT OVER 0; AT atY + i, atX; " ";:NEXT i + ELSE + LET NUM = sc / 10: REM Left digit + GOSUB 5000 + END IF +3040 LET atX = atX + 3 +3050 LET NUM = sc Mod 10 +3060 GOSUB 5000 +3070 NEXT player +3080 GOSUB 6000 +3090 RETURN + +5000 REM Prints Number NUM at atY, atX +5010 FOR ny = 0 TO 2: FOR nx = 0 TO 1 +5020 PRINT OVER 0; AT atY + ny, atX + nx; CHR$(numbers(NUM, ny, nx)); +5030 NEXT nx: NEXT ny +5040 RETURN + +6000 REM Check scores +6010 FOR player = 0 TO 1 +6020 IF score(player) >= pointsToWin THEN + CLS + PRINT AT 10, 10; + IF player = comp THEN + PRINT BOLD 1; "I WIN!" + ELSE + PRINT BOLD 1; "YOU WIN!" + END IF + DO LOOP WHILE INKEY$ <> "" + PAUSE 500 + PRINT AT 21, 3; BOLD 1; "PRESS ANY KEY TO CONTINUE"; + PAUSE 0 + GOTO 30 + END IF +6030 NEXT player +6040 RETURN + +#require "vsync.asm" + diff --git a/examples/sd81/snake_sd81.bas b/examples/sd81/snake_sd81.bas new file mode 100644 index 000000000..8b8231953 --- /dev/null +++ b/examples/sd81/snake_sd81.bas @@ -0,0 +1,165 @@ +1 REM ******************************************************************** +2 REM ZXSnake by Federico J. Alvarez Valero (05-02-2003) +10 REM This program is free software; you can redistribute it and/or modify +11 REM it under the terms of the GNU General Public License as published by +12 REM the Free Software Foundation; either version 2 of the License, or +13 REM (at your option) any later version. +14 REM This program is distributed in the hope that it will be useful, +15 REM but WITHOUT ANY WARRANTY; without even the implied warranty of +16 REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +17 REM GNU General Public License for more details. +18 REM You should have received a copy of the GNU General Public License +19 REM along with this program; if not, write to the Free Software +20 REM Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +30 REM ENGLISH VERSION +40 REM ******************************************************************** + +50 BORDER 7 : PAPER 7 : INK 0 : CLS +51 PRINT AT 3,13 ; PAPER 1 ; INK 7 ; "ZXSnake" +52 PRINT AT 5,9 ; PAPER 7 ; INK 0 ; "Q - Up" +53 PRINT AT 6,9 ; PAPER 7 ; INK 0 ; "A - Down" +54 PRINT AT 7,9 ; PAPER 7 ; INK 0 ; "O - Left" +55 PRINT AT 8,9 ; PAPER 7 ; INK 0 ; "P - Right" +56 PRINT AT 10,3 ; PAPER 7 ; INK 0 ; "You have to pick up every" +57 PRINT AT 11,3 ; PAPER 7 ; INK 0 ; "fruit on the screen while" +58 PRINT AT 12,3 ; PAPER 7 ; INK 0 ; "growing up more and more..." +59 PRINT AT 15,3 ; PAPER 7 ; INK 0 ; "Press any key to start" +60 LET j$ = INKEY$ +61 IF j$ = "" THEN GOTO 60: END IF + +70 REM UDG +71 DIM udg(1, 7) AS uByte => { {60, 66, 129, 129, 129, 129, 66, 60}, _ + {24, 60, 60, 60, 126, 251, 247, 126}} +73 POKE UINTEGER $8002, @udg(0, 0): REM sysvar UDG de zx81sd (en Spectrum era 23675) +74 LET S$ = CHR$(144): LET F$ = CHR$(145) + +75 REM Variable declaration +76 DIM p(23,34) AS UBYTE: REM Screen +77 DIM x(23,34) AS UBYTE: REM Xorientations +78 DIM y(23,34) AS UBYTE: REM Yorientations + +79 DIM c, f AS UBYTE +80 DIM headx, heady AS UBYTE : REM Head coordinates +81 DIM tailx, taily AS UBYTE : REM Tail coordinates +90 DIM score, eaten as ULONG +95 DIM maxx, maxy, minx, miny as UByte + +100 REM Variable definition +110 LET headx = 11 : REM head x coordinate +120 LET heady = 5 : REM head y coordinate +130 LET tailx = 5 : REM tail x coordinate +140 LET taily = 5 : REM tail y coordinate +150 LET orientationx = 1 +160 LET orientationy = 0 + +165 REM Clear arrays p, x and y +170 FOR c = 1 to 23: FOR f = 1 to 34 +180 LET p(c, f) = 0: LET x(c, f) = 0: LET y(c, f) = 0 +190 NEXT f: NEXT c + +200 LET score = 0 +210 LET eaten = 0 +220 LET maxx = 33 +230 LET maxy = 22 +240 LET minx = 0 +250 LET miny = 0 + +1000 REM Screen Initialization +1010 BORDER 1 +1015 CLS +1020 PRINT AT 21,0 ; PAPER 1 ; INK 7 ; " SCORE : " +1030 FOR c = minx TO maxx +1040 LET p(miny+1,c+1) = 4 +1050 LET p(maxy+1,c+1) = 4 +1060 NEXT c +1070 FOR f = miny TO maxy +1080 LET p(f+1,minx+1) = 4 +1090 LET p(f+1,maxx+1) = 4 +1100 NEXT f + +1500 GOSUB 8000 : REM Place first fruit + +2000 REM Draw snake in its initial position +2001 PAPER 7 : INK 0 +2005 REM Draw the body +2010 FOR c = tailx TO headx-1 +2020 PRINT AT taily,c ; INK 0 ; "O" +2025 LET p(taily+2,c+2) = 3 +2026 LET x(taily+2,c+2) = 1 +2027 LET y(taily+2,c+2) = 0 +2030 NEXT c +2040 REM Draw the Head +2050 PRINT AT heady,headx ; INK 0 ; S$; +2055 LET p(heady+2,headx+2) = 2 +2056 LET x(heady+2,headx+2) = 1 +2057 LET y(heady+2,headx+2) = 0 + +3000 REM Update snake position +3005 INK 0 +3010 REM Change the orientation if needed +3015 LET x(heady+2,headx+2) = orientationx +3020 LET y(heady+2,headx+2) = orientationy +3025 REM Erase previous head +3030 PRINT AT heady,headx ; "O" +3035 LET p(heady+2,headx+2) = 3 +3040 LET headx = headx + orientationx +3045 LET heady = heady + orientationy +3050 IF p(heady+2,headx+2) > 1 THEN GOTO 9900: END IF +3051 IF p(heady+2,headx+2) = 1 THEN + LET score = score + 10 : PRINT AT 21,10 ; _ + PAPER 1 ; INK 7 ; score : LET eaten = 1 : GOSUB 8000: END IF +3055 REM Print the new head +3060 PRINT AT heady,headx ; S$; +3065 LET p(heady+2,headx+2) = 2 +3070 IF eaten = 0 THEN GOSUB 8100: END IF +3080 LET eaten = 0 + +3200 REM Read the keyboard +3210 LET a$ = INKEY$ +3220 IF orientationx < 1 AND (a$ = "O" OR a$ = "o") THEN + LET orientationx = -1 : LET orientationy = 0: END IF +3230 IF orientationx > -1 AND (a$ = "P" OR a$ = "p") THEN + LET orientationx = 1 : LET orientationy = 0: END IF +3240 IF orientationy < 1 AND (a$ = "Q" OR a$ = "q") THEN + LET orientationx = 0 : LET orientationy = -1: END IF +3250 IF orientationy > -1 AND (a$ = "A" OR a$ = "a") THEN + LET orientationx = 0 : LET orientationy = 1: END IF + +3500 REM Pausa / Delay +3505 BEEP 0.005, 0 +3510 PAUSE 5 + +7998 GOTO 3000 + +8000 REM Fruit placement +8010 LET fruitx = INT(RND*30)+1 +8020 LET fruity = INT(RND*20)+1 +8030 IF p(fruity+2,fruitx+2) = 0 THEN GOTO 8050: END IF +8040 GOTO 8010 +8050 PRINT AT fruity,fruitx ; INK 2 ; F$; +8060 LET p(fruity+2,fruitx+2) = 1 +8070 RETURN + +8100 REM Erase tail +8110 PRINT AT taily,tailx ; " " +8120 LET newtailx = tailx + x(taily+2,tailx+2) +8130 LET newtaily = taily + y(taily+2,tailx+2) +8140 LET p(taily+2,tailx+2) = 0 +8150 LET x(taily+2,tailx+2) = 0 +8160 LET y(taily+2,tailx+2) = 0 +8170 LET tailx = newtailx +8180 LET taily = newtaily +8190 RETURN + +9900 REM Game over +9910 PRINT AT 10,12 ; INK 2 ; "GAME OVER..." +9920 PRINT AT 11,10 ; INK 2 ; "SCORE : " ; score +9930 PRINT AT 13,10 ; INK 0 ; "Press any key" +9931 REM Mondatory pause so the msg. can be read +9932 FOR i = 0 TO 100 + +9933 NEXT i +9940 LET j$ = INKEY$ +9950 IF j$ <> "" THEN GOTO 100: END IF +9960 GOTO 9940 + From 507aeb8f292d8eced4e102d8f4cd58ce21c1ff18 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 09:46:37 +0200 Subject: [PATCH 12/27] zx81sd: enlazar repositorios relacionados en el README SD81 Booster (hardware/firmware), EightyOne Cross-platform (emulador usado en el desarrollo) y CPM_SD81. Co-Authored-By: Claude Sonnet 5 --- docs/zx81sd/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/zx81sd/README.md b/docs/zx81sd/README.md index e8d7dd7de..0e7741e92 100644 --- a/docs/zx81sd/README.md +++ b/docs/zx81sd/README.md @@ -77,3 +77,12 @@ fuentes de depuración/diagnóstico usados durante el desarrollo del port viven en un repositorio complementario, no en este. Ver [USO.md](USO.md) para el flujo completo de compilar → empaquetar → cargar. + +## Repositorios relacionados + +- **[SD81 Booster](https://codeberg.org/Retrostuff/SD81-Booster)** — + firmware/hardware de la interfaz para la que se ha hecho este port. +- **[EightyOne Cross-platform](https://codeberg.org/wilco2009/EightyOne-CrossPlatform)** — + emulador usado durante el desarrollo para probar sin hardware real. +- **[CPM_SD81](https://codeberg.org/wilco2009/CPM_SD81)** — CP/M sobre + el SD81 Booster. From a469f4aa5c7202d4a61d1b59db52e9c7d0eb16f7 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 09:53:18 +0200 Subject: [PATCH 13/27] zx81sd: precisar estado de pendientes de stdlib compartida winscroll se cree portado/probado pero sin auditoria formal; putchars/ puttile sin auditar (sin ROM detectada a simple vista); screen.bas SI depende de ROM (STK_END/S_SCRNS_BC/RECLAIM2); print42/print64 SI dependen de sysvars Spectrum (UDG/ATTR_P) y son el siguiente objetivo. Co-Authored-By: Claude Sonnet 5 --- docs/zx81sd/README.md | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/docs/zx81sd/README.md b/docs/zx81sd/README.md index 0e7741e92..6874d7f63 100644 --- a/docs/zx81sd/README.md +++ b/docs/zx81sd/README.md @@ -35,13 +35,29 @@ beeper), teclado (INKEY$/INPUT sobre el teclado físico del ZX81), joystick, la librería MCU completa (ficheros, RTC/BAT, voz, mapeador de memoria...) y LOAD/SAVE/VERIFY...CODE contra SD. -Pendiente / sin auditar: el resto de utilidades de pantalla de la -stdlib compartida (`winscroll`, `putchars`, `puttile`, `screen`, -`print42/64`...) — puede que alguna dependa de la ROM del Spectrum -igual que `scroll.bas` (ya arreglado, ver [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md)). -El port de `maskedsprites.bas`/MSFS (sprites enmascarados sobre el -mapeador de memoria) también está en proceso, aún sin terminar de -funcionar bien. +Pendiente / sin auditar, resto de utilidades de pantalla de la stdlib +compartida: + +- `winscroll.bas`: se cree ya portado y probado, pero sin confirmar con + una auditoría formal (no hay override en `zx81sd/stdlib/` — de ser + cierto, es porque no necesitaba ninguno, como `scroll.bas` antes del + fix o `4inarow.bas`). +- `putchars.bas`/`puttile.bas`: sin auditar ni probar. Un vistazo rápido + al fuente no encuentra direcciones de ROM/sysvars del Spectrum + (`putChars` rellena un rectángulo de caracteres, `putTile` coloca un + tile de 16×16 px), así que son buenos candidatos a funcionar sin + cambios, pero no está confirmado. +- `screen.bas`: **sí depende de la ROM** (`$2538`/`$5C65`/`$19E8`, + rutinas y sysvars fijas del Spectrum para leer de vuelta un carácter + de pantalla) — necesitará un override real, no solo una auditoría. +- `print42.bas`/`print64.bas`: **sí dependen de sysvars del Spectrum** + (`$5C7B` = UDG, `23693` = ATTR_P) — confirmado pendiente de migrar, + próximo objetivo. + +Ver [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md) para el patrón general de este +tipo de fix. El port de `maskedsprites.bas`/MSFS (sprites enmascarados +sobre el mapeador de memoria) también está en proceso, aún sin terminar +de funcionar bien. ## Documentación From 488f991ed8f6ca7e67a3a564064bb743ad13ddc5 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 11:55:28 +0200 Subject: [PATCH 14/27] zx81sd: portar print42.bas/print64.bas (PRINT de 42/64 columnas) Overrides completos de la libreria compartida (Britlion): las sysvars fijas de la ROM del Spectrum (UDG $5C7B, CHARS-256 15360, ATTR_P 23693) se sustituyen por los sysvars propios de zx81sd, y las constantes de base de pantalla/atributos (antes literales de $4000/$5800) se parchean en tiempo de ejecucion via automodificacion de codigo, leyendo el byte alto real de SCREEN_ADDR/SCREEN_ATTR_ADDR. Bug real encontrado durante el port: un #include once dentro de estos ficheros, si resultaba ser la primera inclusion de todo el programa, arrastraba bootstrap.asm/charset.asm (con su INCBIN del font completo) justo en el punto del fichero fuente donde se puso el include -- a nivel BASIC eso da "illegal preprocessor character", y dentro de un bloque ASM se emite como codigo ejecutable real (HALT en direccion basura). Fix: no incluir sysvars.asm desde estos ficheros, solo referenciar los simbolos con el prefijo .core. (confiando en que CLS/PRINT ya lo trajeron). Documentado en PRECAUCIONES.md (nueva seccion 8) y CAMBIOS_BASIC.md (nueva seccion 9) como gotcha general para cualquier libreria nueva que necesite sysvars propios. Verificado por simulacion (examples/sd81/print4264.bas): ambas cadenas de prueba se renderizan legibles pixel a pixel, sin corrupcion de memoria, con HALT normal en __END_PROGRAM. Pendiente de confirmar en emulador/hardware real. Co-Authored-By: Claude Sonnet 5 --- docs/zx81sd/CAMBIOS_BASIC.md | 64 +++ docs/zx81sd/PRECAUCIONES.md | 36 ++ docs/zx81sd/README.md | 9 +- examples/sd81/print4264.bas | 13 + src/lib/arch/zx81sd/stdlib/print42.bas | 586 +++++++++++++++++++++++++ src/lib/arch/zx81sd/stdlib/print64.bas | 320 ++++++++++++++ 6 files changed, 1025 insertions(+), 3 deletions(-) create mode 100644 examples/sd81/print4264.bas create mode 100644 src/lib/arch/zx81sd/stdlib/print42.bas create mode 100644 src/lib/arch/zx81sd/stdlib/print64.bas diff --git a/docs/zx81sd/CAMBIOS_BASIC.md b/docs/zx81sd/CAMBIOS_BASIC.md index 5228bfff4..56eaa17fa 100644 --- a/docs/zx81sd/CAMBIOS_BASIC.md +++ b/docs/zx81sd/CAMBIOS_BASIC.md @@ -272,6 +272,63 @@ de `Map()`/`MapGet` sobre el bloque 7. --- +## 9. `print42.bas`/`print64.bas` — librerías nuevas (sin cambios de ejemplo) + +`stdlib/print42.bas` y `stdlib/print64.bas` (Britlion) implementan un +`PRINT` de 42/64 columnas dibujando pixel a pixel, en vez de usar el +`PRINT` normal de 32 columnas. La versión zx48k depende de la ROM del +Spectrum en varios sitios: + +- `print42.bas`: `ld hl,$5C7B` (sysvar `UDG`), `ld de,15360` + (`CHARS-256` fijo) y `ld a,(23693)` (sysvar `ATTR_P`). +- `print64.bas`: solo `ld a,(23693)` (`ATTR_P`) — su charset de 4px es + propio, no usa `UDG`/`CHARS`. +- Ambos, además, sitúan la pantalla y los atributos con constantes + **inmediatas** de base fija (`add a,64` / `add a,88`, los bytes altos + de `$4000`/`$5800`) en vez de leer un puntero — funciona en el + Spectrum porque esas direcciones son literales de ROM, pero en zx81sd + `SCREEN_ADDR`/`SCREEN_ATTR_ADDR` son variables (bloque 6, `$C000`/ + `$D800`). + +Se creó `src/lib/arch/zx81sd/stdlib/print42.bas` y `print64.bas` +(overrides completos): sysvars fijas → sus equivalentes zx81sd +(`UDG`/`CHARS`/`ATTR_P`, ver `sysvars.asm`), y las dos constantes de +base de pantalla/atributos se **parchean una vez al entrar en la +función** (automodificación de código sobre la propia instrucción +`ADD A,n`), leyendo el byte alto real de `SCREEN_ADDR`/ +`SCREEN_ATTR_ADDR` en ese momento — igual que hacía la ROM original con +sus propias rutinas, solo que con un valor variable en vez de fijo. + +**Bug real cazado durante el port** (no en el ejemplo, en la librería +misma): un `#include once ` puesto ingenuamente al +principio del fichero (nivel BASIC, fuera de cualquier bloque `ASM`) +producía `error: illegal preprocessor character`, y puesto dentro del +bloque `ASM` de la función compilaba pero colgaba el programa en +tiempo de ejecución (`HALT` en una dirección de código basura). Causa: +si esta librería es la **primera** en incluir `sysvars.asm` en todo el +programa, arrastra tras de sí `bootstrap.asm`/`charset.asm` — y este +último hace `INCBIN "specfont.bin"` (el font completo, no texto +ensamblador) — justo en ese punto del fichero fuente. Puesto a nivel +BASIC, el lexer de BASIC intenta tokenizar bytes binarios de fuente +como si fueran texto (de ahí el "illegal preprocessor character"). +Puesto dentro de la función, el binario del font se emite literalmente +en medio del cuerpo compilado de la función, y la CPU lo "ejecuta" +como si fueran instrucciones en cuanto el flujo cae ahí. Fix: no +incluir `sysvars.asm` en absoluto desde estos ficheros — confían en que +ya esté incluido por el resto del runtime (`CLS`/`PRINT`, que cualquier +programa que use `print42`/`print64` va a usar también), y solo +referencian los símbolos ya namespaced (`.core.CHARS`, etc.). Detalle +completo en [PRECAUCIONES.md](PRECAUCIONES.md) y [MAP.md](MAP.md). + +Verificado por simulación (arnés Python de siempre): las dos cadenas de +prueba (`tests_debug/print4264.bas`, repositorio complementario) se +renderizan legibles píxel a píxel en la posición esperada, sin +escrituras fuera de pantalla/atributos/variables propias del fichero, y +el programa llega a su `__END_PROGRAM`/`HALT` normal. Pendiente de +probar en el emulador/hardware real. + +--- + ## Resumen para el manual | Ejemplo | Copia en zx81sd | Cambios de fuente | Flags de compilación | @@ -284,6 +341,7 @@ de `Map()`/`MapGet` sobre el bloque 7. | maskedsprites.bas | `maskedsprites_sd81.bas` | `WaitForNewFrame` reescrita (EI+HALT → VSYNC_TICK); librería `cb/maskedsprites.bas` portada al mapeador (bloque 7) — **en proceso, aún no funciona del todo bien** | ninguna especial | | pong.bas (no oficial) | `pong.bas` en `examples/sd81/` | 1 línea ASM (namespace de VSYNC_TICK) | ninguna especial | | block7test.bas (no oficial) | `block7test.bas` en `examples/sd81/` | n/a (escrito directamente para zx81sd) | ninguna especial | +| print42.bas/print64.bas (librerías) | no aplica (no son ejemplos) | ninguno — fix fue en `stdlib/print42.bas`/`print64.bas` | ninguna especial | Patrón identificado para futuros ejemplos: @@ -302,3 +360,9 @@ Patrón identificado para futuros ejemplos: `push namespace core` (como `VSYNC_TICK`) necesita el prefijo `.core.` si el bloque `ASM` que lo invoca no está ya dentro de ese namespace (ver caso de `pong.bas` arriba). +4. Si una librería nueva necesita sysvars propios (`CHARS`/`UDG`/ + `ATTR_P`/`SCREEN_ADDR`/...), **no hacer `#include once ` + dentro del fichero** — solo referenciar los símbolos con el prefijo + `.core.`, confiando en que el resto del runtime (`CLS`/`PRINT`) ya lo + incluyó. Ver caso de `print42.bas`/`print64.bas` arriba y + [PRECAUCIONES.md](PRECAUCIONES.md). diff --git a/docs/zx81sd/PRECAUCIONES.md b/docs/zx81sd/PRECAUCIONES.md index e1c543fd6..a83c15551 100644 --- a/docs/zx81sd/PRECAUCIONES.md +++ b/docs/zx81sd/PRECAUCIONES.md @@ -175,6 +175,42 @@ paquete `z80` de Python. Dos lecciones ya aprendidas por las malas repositorio del emulador, no en este) antes que del runtime — en hardware real esos traps no existen. +## 8. Nunca hagas `#include once ` desde una librería propia + +Si necesitas los sysvars propios de zx81sd (`CHARS`/`UDG`/`ATTR_P`/ +`SCREEN_ADDR`/`SCREEN_ATTR_ADDR`...) desde un fichero `stdlib/*.bas` +nuevo, **no lo incluyas tú mismo con `#include once `** — +solo referencia los símbolos con el prefijo `.core.` (ver punto 5) y +confía en que el resto del runtime ya lo trajo. + +`sysvars.asm` arrastra tras de sí `bootstrap.asm` → `charset.asm`, y +este último hace `INCBIN "specfont.bin"` (el font completo, bytes +binarios, no texto ensamblador). Si tu fichero resulta ser el +**primero** en incluir `sysvars.asm` en todo el programa compilado +(fácil que pase: un `#include ` al principio del fuente +del usuario se procesa antes que cualquier `CLS`/`PRINT` textualmente +posterior), ese `INCBIN` se emite **justo en el punto del fichero fuente +donde pusiste el `#include`**: + +- Puesto a nivel BASIC (fuera de un bloque `ASM ... END ASM`): el lexer + de BASIC intenta tokenizar esos bytes binarios como si fueran texto + fuente → `error: illegal preprocessor character` en líneas que no + tienen nada que ver con el problema real (mal atribuidas al inicio + del fichero). +- Puesto dentro de un bloque `ASM` (p. ej. al principio del cuerpo de + una función): el binario del font se emite literalmente en medio del + código compilado de esa función — compila sin error, pero la CPU + "ejecuta" esos bytes de font como si fueran instrucciones en cuanto + el flujo de control cae ahí, produciendo un `HALT` o comportamiento + errático en una dirección que no tiene relación aparente con el bug + (encontrado al portar `print42.bas`/`print64.bas`, ver + [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md)). + +Cualquier programa real que use tu librería casi seguro que también usa +`CLS`/`PRINT` en algún punto, y esas rutinas ya requieren `sysvars.asm` +— así que omitir el `#include` en tu fichero es seguro en la práctica, +no una chapuza. + ## Ver también - [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md) — catálogo de cambios de fuente diff --git a/docs/zx81sd/README.md b/docs/zx81sd/README.md index 6874d7f63..9bf6ff056 100644 --- a/docs/zx81sd/README.md +++ b/docs/zx81sd/README.md @@ -50,9 +50,12 @@ compartida: - `screen.bas`: **sí depende de la ROM** (`$2538`/`$5C65`/`$19E8`, rutinas y sysvars fijas del Spectrum para leer de vuelta un carácter de pantalla) — necesitará un override real, no solo una auditoría. -- `print42.bas`/`print64.bas`: **sí dependen de sysvars del Spectrum** - (`$5C7B` = UDG, `23693` = ATTR_P) — confirmado pendiente de migrar, - próximo objetivo. +- `print42.bas`/`print64.bas`: **portados** (override completo en + `stdlib/`) — sysvars fijas → equivalentes zx81sd, y las constantes de + base de pantalla/atributos se parchean en tiempo de ejecución + (automodificación de código) en vez de ser fijas. Verificado por + simulación (texto legible píxel a píxel, sin corrupción de memoria); + pendiente de confirmar en el emulador/hardware real. Ver [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md) para el patrón general de este tipo de fix. El port de `maskedsprites.bas`/MSFS (sprites enmascarados diff --git a/examples/sd81/print4264.bas b/examples/sd81/print4264.bas new file mode 100644 index 000000000..99c60d5f0 --- /dev/null +++ b/examples/sd81/print4264.bas @@ -0,0 +1,13 @@ +#include +#include + +CLS +PRINT AT 0, 0; "print42/print64 test" + +printat42(3, 0) +print42("HELLO 42 COLUMNS TEST 0123456789") + +printat64(10, 0) +print64("HELLO 64 COLUMNS TEST 0123456789") + +PAUSE 0 diff --git a/src/lib/arch/zx81sd/stdlib/print42.bas b/src/lib/arch/zx81sd/stdlib/print42.bas new file mode 100644 index 000000000..605676c28 --- /dev/null +++ b/src/lib/arch/zx81sd/stdlib/print42.bas @@ -0,0 +1,586 @@ +' vim:ts=4:et: + +#ifndef __PRINT42__ +#define __PRINT42__ + +' -------------------------------------------------- +' A PRINT Routine with 42 COLUMNS! +' +' Contributed by Britlion +' +' Override zx81sd: la version zx48k calcula direcciones de pantalla y +' atributos con constantes fijas de la ROM del Spectrum ($4000/$5800, +' aqui "64"/"88" como bytes altos) y con sysvars fijos de la ROM +' ($5C7B = UDG, 23693 = ATTR_P, 15360 = CHARS-256). En zx81sd: +' - UDG/ATTR_P/CHARS son sysvars propios en $8000+ (ver sysvars.asm, +' namespace core, de ahi el prefijo .core. en las referencias de +' mas abajo), y CHARS/UDG son VARIABLES (punteros), no constantes, +' asi que se leen con LD (r16),(CHARS) en vez de un LD inmediato. +' - SCREEN_ADDR/SCREEN_ATTR_ADDR tambien son variables (no EQU fijos: +' bloque 6 en $C000/$D800 en vez de $4000/$5800), pero el byte alto +' se sigue sumando dentro de un ADD A,n con constante inmediata (el +' algoritmo de "tercios" de pantalla es identico al del Spectrum, +' solo cambia la base). Se resuelve parcheando esa constante una +' vez al entrar en print42(), leyendola de SCREEN_ADDR+1/ +' SCREEN_ATTR_ADDR+1 (automodificacion de codigo, igual que hacia +' la ROM original con sus propias rutinas). +' -------------------------------------------------- + +#pragma push(case_insensitive) +#pragma case_insensitive = TRUE + +SUB printat42 (y as uByte, x as uByte) + POKE @printAt42Coords,x + POKE @printAt42Coords+1,y +END SUB + + +SUB print42(characters$ as string) +asm + ; No hace '#include once ' aqui: si esta libreria fuera + ; la primera en incluirlo en todo el programa, arrastraria tambien + ; bootstrap.asm/charset.asm (con su INCBIN del font completo) justo + ; en medio de este cuerpo de funcion, ejecutandose como si fueran + ; instrucciones (bug real, encontrado al portar este fichero). Las + ; referencias a CHARS/UDG/ATTR_P/SCREEN_ADDR/SCREEN_ATTR_ADDR de mas + ; abajo confian en que sysvars.asm ya este incluido por el resto del + ; runtime (CLS/PRINT lo requieren siempre, y este fichero no tiene + ; sentido usarlo sin ellos). + PROC + + ; Parchea las constantes de base de pantalla/atributos usadas mas + ; abajo (ver __P42_SCR_HI/__P42_ATTR_HI) con los bytes altos reales + ; de .core.SCREEN_ADDR/.core.SCREEN_ATTR_ADDR de este programa (variables en + ; zx81sd, no EQU fijos como en el Spectrum). + LOCAL __P42_SCR_HI, __P42_ATTR_HI + ld a, (.core.SCREEN_ADDR+1) + ld (__P42_SCR_HI+1), a + ld a, (.core.SCREEN_ATTR_ADDR+1) + ld (__P42_ATTR_HI+1), a + + LD A, H + OR L + JP Z, print42end + + LD C,(HL) + INC HL + LD B,(HL) ; all told, LD BC with the length of the string. + + LD A, C + OR B + JP Z, print42end ; Is the length of the string 0? If so, quit. + + INC HL ;Puts HL to the first real character in the string. + +LOCAL examineChar +examineChar: + LD A,(HL) ; Grab the character at our pointer position + CP 165 ; Too high to print? + JR NC, nextChar ; Then we go to the next + + CP 144 + JR NC, prn ; char is a UDG + + CP 128 + JR NC, nextChar ; char is a block graphic + + CP 22 ; Is this an AT? + JR NZ, isNewline ; If not jump over the AT routine to isNewline + +LOCAL isAt +isAt: + EX DE,HL ; Get DE to hold HL for a moment + ;;AND A ; Plays with the flags. One of the things it does is reset Carry. + ;;LD HL,00002 + ;;SBC HL,BC ; Subtract length of string from HL. + LD HL, -2 + ADD HL, BC + EX DE,HL ; Get HL back from DE + JP NC, print42end ; If the result WASN'T negative, return. (We need AT to have parameters to make sense) + + INC HL ; Onto our Y co-ordinate + LD D,(HL) ; Put it in D + DEC BC ; and move our string remaining counter down one + INC HL ; Onto our X co-ordinate + LD E,(HL) ; Put the next one in E + DEC BC ; and move our string remaining counter down one + ld (xycoords), de + JR nextChar + +LOCAL isNewline +isNewline: + CP 13 ; Is this character a newline? + JR NZ, checkdel ; If not, jump forward + +LOCAL newline +newline: + ld de, (xycoords) + CALL nxtline ; move to next line + ld (xycoords), de + JR nextChar + +LOCAL checkdel +checkdel: + CP 8 + JR NZ, checkvalid + ld de, (xycoords) + dec de + ld (xycoords), de + ld a, 41 + cp e + JR NC, nextChar + ld e, a + ld (xycoords), de + ld a, 23 + cp d + JR NC, nextChar + ld d, a + ld (xycoords), de + JR nextChar + +LOCAL checkvalid +checkvalid: + CP 31 ; Is character <31? + JR C, nextChar ; If not go to next character + +LOCAL prn +prn: + PUSH HL ; Save our position + PUSH BC ; Save our countdown of chars left + CALL printachar ; Go print a character + POP BC ; Recover our count + POP HL ; Recover our position + +LOCAL nextChar +nextChar: + INC HL ; Move to the next position + DEC BC ; count off a character + LD A,B + OR C ; Did we hit the end of our string? (BC=0?) + JR NZ, examineChar ; If not, we need to go look at the next character. + JP print42end ; End the print routine + + +; This routine forms the new 6-bit wide characters and +; alters the colours to match the text. The y,x co-ordinates and eight +; bytes of workspace are located at the end of this chunk. +; it starts with the character ascii code in the accumulator + +LOCAL printachar +printachar: + EXX + PUSH HL ; Store H'L' where we can get it. + EXX + + CP 144 + jr nc,printudg + + ld c, a ; Put a copy of the character in C + ld h, 0 + ld l, a ; Put the Character in HL + + ld de, whichcolumn-32 ; the character is at least 32, so space = 0th entry. + add hl, de ; HL -> table entry for char. + ld a, (hl) ; Load our column slice data from the table. + cp 32 ; Is it less than 32? + jr nc, calcChar ; If so, go to the calculated character subroutine + +; This is the special case 'we defined the character in the table' option + ld de, characters ; Point DE at our table + ld l, a ; Put our character number from our table lookup that's in HL in a + call mult8 ; multiplies L by 8 and adds in DE [so HL points at our table entry] + ld b, h + ld c, l ; Copy our character data address into BC + jr printdata ; We have our data source, so we print it. + +LOCAL printudg +printudg + sub 144 ; get the udg offset + ld hl, .core.UDG ; examine our own .core.UDG sysvar for the udg address + ld e,(hl) ; lsb for udg address + inc hl + ld d,(hl) ; mbs for udg address + ld l,a ; character offset for udg + call mult8 ; multiplies L by 8 and adds in DE [so HL points at our table entry] + ld b, h + ld c, l ; Copy our character data address into BC + jr printdata ; We have our data source, so we print it. + +LOCAL calcChar +calcChar: ; this is the calculate-from-charset option + ; a holds the column kill data + ld de, (.core.CHARS) ; Character set-256 (variable en zx81sd, no 15360 fijo) + ld l, c ; Get our character back from C + call mult8 ; Multiply l by 8 and add to DE. (HL points at the charset data for our character now) + + ld de, workspace ; Point DE at our 8 byte workspace. + push de ; Save it + exx ; + ld c, a ; Put our kill column in C' + cpl ; Invert + ld b, a ; Put the inverse in B' + exx ; + ld b, 8 ; 8 bytes to a character loop counter + +LOCAL loop1 +loop1: + ld a, (hl) ; Load a byte of character data + inc hl ; point at the next byte + exx ; + ld e, a ; Put it in e' + and c ; keep the left column block we're using + ld d, a ; and put it in d' + ld a, e ; grab our original back + rla ; shift it left (which pushes out our unwanted column) + and b ; keep just the right block + or d ; mix with the left block + exx ; + ld (de), a ; put it into our workspace + inc de ; next workspace byte + djnz loop1 ; go round for our other bytes + + pop bc ; Recover a pointer to our workspace. + +LOCAL printdata +printdata: + call testcoords ; check our position, and wrap around if necessary. [returns with d=y,e=x] + inc e ; Bump along to next co-ordinate + ld (xycoords), de ; Store our coordinates for the next character + dec e ; Bump back to our current one + ld a, e ; get x + sla a ; Shift Left Arithmetic - *2 + ld l, a ; put x*2 into L + sla a ; make it x*4 + add a, l ; (x*2)+(x*4)=6x + ld l, a ; put 6x into L [Since we're in a 6 pixel font, L now contains the # of first pixel we're interested in] + srl a ; divide by 2 + srl a ; divide by another 2 (/4) + srl a ; divide by another 2 (/8) + ld e, a ; Put the result in e (Since the screen has 8 pixel bytes, pixel/8 = which char pos along our first pixel is in) + ld a, l ; Grab our pixel number again + and 7 ; And do mod 8 [So now we have how many pixels into the character square we're starting at] + push af ; Save A + ex af, af' + ld a, d ; Put y Coord into A' + sra a ; Divide by 2 + sra a ; Divide by another 2 (/4 total) + sra a ; Divide by another 2 (/8) [Gives us a 1/3 of screen number] +__P42_ATTR_HI: + add a, 88 ; Add in start of screen attributes high byte (parcheado con .core.SCREEN_ATTR_ADDR+1 al entrar en print42) + ld h, a ; And put the result in H + ld a, d ; grab our Y co-ord again + and 7 ; Mod 8 (why? *I thought to give a line in this 1/3 of screen, but we're in attrs here) + rrca ; + rrca + rrca ; Bring the bottom 3 bits to the top - Multiply by 32 + ; (since there are 32 bytes across the screen), here, in other words. [Faster than 5 SLA instructions] + add a, e ; add in our x coordinate byte to give us a low screen byte + ld l, a ; Put the result in L. So now HL -> screen byte at the top of the character + + ld a, (.core.ATTR_P) ; ATTR P Permanent current colours, etc (as set up by colour statements). + ld e, a ; Copy ATTR into e + ld (hl), e ; Drop ATTR value into screen + inc hl ; Go to next position along + pop af ; Pull how many pixels into this square we are + cp 3 ; It more than 2? + jr c, hop1 ; No? It all fits in this square - jump changing the next attribute + + ld (hl), e ; Must be yes - we're setting the attributes in the next square too. + +LOCAL hop1 +hop1: + dec hl ; Back up to last position + ld a, d ; Y Coord into A' + and 248 ; Turn it into 0,8 or 16. (y=0-23) +__P42_SCR_HI: + add a, 64 ; Turn it into screen bitmap high byte (parcheado con .core.SCREEN_ADDR+1 al entrar en print42) + ld h, a ; Stick it in H + push hl ; Save it + exx ; Swap registers + pop hl ; Put it into H'L' + exx ; Swap Back + ld a, 8 + +LOCAL hop4 +hop4: + push af ; Save Accumulator + ld a, (bc) ; Grab a byte of workspace + exx ; Swap registers + push hl ; Save h'l' + ld c, 0 ; put 0 into c' + ld de, 1023 ; Put 1023 into D'E' + ex af, af' ; Swap AF + and a ; Flags on A + jr z, hop3 ; If a is zero jump forward + + ld b, a ; A -> B + ex af, af' ; Swap to A'F' + +LOCAL hop2 +hop2:; Slides a byte right to the right position in the block (and puts leftover bits in the left side of c) + and a ; Clear Carry Flag + rra ; Rotate Right A + rr c ; Rotate right C (Rotates a carry flag off A and into C) + scf ; Set Carry Flag + rr d ; Rotate Right D + rr e ; Rotate Right E (D flows into E, with help from the carry bit) + djnz hop2 ; Decrement B and loop back + + ex af, af' + +LOCAL hop3 +hop3: + ex af, af' + ld b, a + ld a, (hl) + and d + or b + ld (hl), a ; Write out our byte + inc hl ; Go one byte right + ld a, (hl) ; Bring it in + and e + or c ; mix those leftover bits into the next block + ld (hl), a ; Write it out again + pop hl + inc h ; Next line + exx + inc bc ; Next workspace byte + pop af + dec a + jr nz, hop4 ; And go back! + + exx ; Tidy up + pop hl ; Clear stack leftovers + exx ; And... + ret ; Go home. + +LOCAL mult8 +mult8: ; Multiplies L by 8 -> HL and adds it to DE. Used for 8 byte table vectors. + ld h, 0 + add hl, hl + add hl, hl + add hl, hl + add hl, de + ret + +LOCAL testcoords +testcoords: + ld de, (xycoords) ; get our current screen co-ordinates (d=y,e=x - little endian) + +LOCAL nxtchar +nxtchar: + ld a, e + cp 42 ; Are we >42? + jr c, ycoord ; if not, hop forward + +LOCAL nxtline +nxtline: + inc d ; if so, so bump us to the next line down + ld e, 0 ; and reset x to left edge + +LOCAL ycoord +ycoord: + ld a, d + cp 24 ; are we >24 lines? + ret c ; if no, exit subroutine + ld d, 0 ; if yes, wrap around to top line again. + ret ; exit subroutine +end asm +printAt42Coords: +asm +LOCAL xycoords +xycoords: + defb 0 ; x coordinate + defb 0 ; y coordinate + +LOCAL workspace +workspace: + defb 0 + defb 0 + defb 0 + defb 0 + defb 0 + defb 0 + defb 0 + defb 0 + +; The data below identifies a column in the character to remove. It consists of 1's +; from the left edge. First zero bit is the column we're removing. +; If the leftmost bit is NOT 1, then the byte represents a redefined character position +; in the lookup table. + +LOCAL whichcolumn +whichcolumn: + defb 254 ; SPACE + defb 254 ; ! + defb 128 ; "" + defb 224 ; # + defb 128 ; $ + defb 0 ; % (Redefined below) + defb 1 ; & (Redefined below) + defb 128 ; ' + defb 128 ; ( + defb 128 ; ) + defb 128 ; * + defb 128 ; + + defb 128 ; , + defb 128 ; - + defb 128 ; . + defb 128 ; / + defb 2 ; 0 (Redefined below) + defb 128 ; 1 + defb 224 ; 2 + defb 224 ; 3 + defb 252 ; 4 + defb 224 ; 5 + defb 224 ; 6 + defb 192 ; 7 + defb 240 ; 8 + defb 240 ; 9 + defb 240 ; : + defb 240 ; ; + defb 192 ; < + defb 240 ; = + defb 192 ; > + defb 192 ; ? + defb 248 ; @ + defb 240 ; A + defb 240 ; B + defb 240 ; C + defb 240 ; D + defb 240 ; E + defb 240 ; F + defb 240 ; G + defb 240 ; H + defb 128 ; I + defb 240 ; J + defb 192 ; K + defb 240 ; L + defb 240 ; M + defb 248 ; N + defb 240 ; O + defb 240 ; P + defb 248 ; Q + defb 240 ; R + defb 240 ; S + defb 3 ; T + defb 240 ; U + defb 240 ; V + defb 240 ; W + defb 240 ; X + defb 4 ; Y + defb 252 ; Z + defb 224 ; [ + defb 252 ; \ + defb 240 ; ] + defb 252 ; ^ + defb 6 ; _ + defb 240 ; UK Pound (Currency) Symbol + defb 255 ; a + defb 128 ; b + defb 255 ; c + defb 255 ; d + defb 255 ; e + defb 255 ; f + defb 255 ; g + defb 255 ; h + defb 255 ; i + defb 255 ; j + defb 255 ; k + defb 255 ; l + defb 255 ; m + defb 255 ; n + defb 255 ; o + defb 255 ; p + defb 255 ; q + defb 255 ; r + defb 255 ; s + defb 255 ; t + defb 255 ; u + defb 255 ; v + defb 255 ; w + defb 255 ; x + defb 255 ; y + defb 255 ; z + defb 128 ; { + defb 128 ; | + defb 255 ; } + defb 128 ; ~ + defb 5 ; (c) end column data + + +LOCAL characters +characters: + defb 0 ; % + defb 0 + defb 100 + defb 104 + defb 16 + defb 44 + defb 76 + defb 0 + + defb 0 ; & + defb 32 + defb 80 + defb 32 + defb 84 + defb 72 + defb 52 + defb 0 + + defb 0 ; digit 0 + defb 56 + defb 76 + defb 84 + defb 84 + defb 100 + defb 56 + defb 0 + + defb 0 ; Letter T + defb 124 + defb 16 + defb 16 + defb 16 + defb 16 + defb 16 + defb 0 + + defb 0 ; Letter Y + defb 68 + defb 68 + defb 40 + defb 16 + defb 16 + defb 16 + defb 0 + + defb 0 ; (c) symbol + defb 48 + defb 72 + defb 180 + defb 164 + defb 180 + defb 72 + defb 48 + + defb 0 + defb 0 + defb 0 + defb 0 + defb 0 + defb 0 + defb 0 + defb 0xFC + +LOCAL print42end +print42end: + ENDP + +end asm +END SUB + +#pragma pop(case_insensitive) + +#endif diff --git a/src/lib/arch/zx81sd/stdlib/print64.bas b/src/lib/arch/zx81sd/stdlib/print64.bas new file mode 100644 index 000000000..bd6c134c5 --- /dev/null +++ b/src/lib/arch/zx81sd/stdlib/print64.bas @@ -0,0 +1,320 @@ +' vim:ts=4:et: +' --------------------------------------------------------- +' 64 Characters wide PRINT Routine for ZX BASIC +' Contributed by Britlion +' +' Override zx81sd: la version zx48k asume las direcciones fijas de la +' ROM del Spectrum para pantalla/atributos ($4000/$5800, aqui "64"/"88" +' como bytes altos) y el sysvar fijo ATTR_P (23693). En zx81sd, +' SCREEN_ADDR/SCREEN_ATTR_ADDR son variables (bloque 6, $C000/$D800) y +' ATTR_P vive en $800E (namespace core, ver sysvars.asm -- de ahi el +' prefijo .core. en las referencias de mas abajo). El charset de este +' fichero (p64_charset) es propio, no depende de la ROM, asi que no +' hace falta tocarlo. Igual que en print42.bas, las dos constantes de +' base de pantalla/atributos se parchean una vez al entrar en +' print64(), leyendo el byte alto real de SCREEN_ADDR/SCREEN_ATTR_ADDR. +' --------------------------------------------------------- + +#ifndef __PRINT64__ +#define __PRINT64__ + +#pragma push(case_insensitive) +#pragma case_insensitive = TRUE + +' Changes print coordinates. +SUB printat64 (y as uByte, x as uByte) + POKE @p64coords,x + POKE @p64coords+1,y +END sub + +' Print given string at current position +SUB print64 (characters$ as String) + asm + ; No hace '#include once ' aqui: si esta libreria fuera + ; la primera en incluirlo en todo el programa, arrastraria tambien + ; bootstrap.asm/charset.asm (con su INCBIN del font completo) justo + ; en medio de este cuerpo de funcion, ejecutandose como si fueran + ; instrucciones (bug real, encontrado al portar este fichero). Las + ; referencias a ATTR_P/SCREEN_ADDR/SCREEN_ATTR_ADDR de mas abajo + ; confian en que sysvars.asm ya este incluido por el resto del + ; runtime (CLS/PRINT lo requieren siempre, y este fichero no tiene + ; sentido usarlo sin ellos). + PROC ; Declares begin of procedure + ; so we can now use LOCAL labels + + ; Parchea las constantes de base de pantalla/atributos usadas mas + ; abajo (ver p64_SCR_HI/p64_ATTR_HI) con los bytes altos reales de + ; .core.SCREEN_ADDR/.core.SCREEN_ATTR_ADDR de este programa. + LOCAL p64_SCR_HI, p64_ATTR_HI + ld a, (.core.SCREEN_ADDR+1) + ld (p64_SCR_HI+1), a + ld a, (.core.SCREEN_ATTR_ADDR+1) + ld (p64_ATTR_HI+1), a + + LD L,(IX+4) + LD H,(IX+5) ; Get String address of characters$ into HL. + + ld a, h + or l + jp z, p64_END ; Return if NULL string + + ; Load BC with length of string, and move HL to point to first character. + ld c, (hl) + inc hl + ld b, (hl) + inc hl + + ; Test string length. If Zero, exit. + ld a, c + or b + jp z, p64_END + + LOCAL examineChar + examineChar: + ld a, (hl) ; Grab the character + cp 128 ; too high to print? + jr nc, nextChar ; then we go to next. + + cp 22 ; Is this an AT? + jr nz, newLine ; If not, hop to newLine routine. + ex de, hl ; Swap DE and HL + and a ; Clear Carry + ld hl, 2 ; + sbc hl, bc ; Can we Shorten our string length by 2? If not then at (y,x) doesn't make sense. + ex de, hl ; Swap DE and HL back + jp nc, p64_END ; If we went negative, there wasn't anything to AT to, so we return. + + inc hl ; Onto our Y co-ordinate + ld d, (hl) ; And load it into D + dec bc ; Shorten our remaining string counter. + inc hl ; Onto the X Co-ordinate + ld e, (hl) ; Load it into E + dec bc ; Shorten our remaining string counter + call p64_test_X ; Make xy legal + jr p64_eaa3 ; Go to save coords + + LOCAL newLine + newLine: + cp 13 ; Is this a newline character? + jr nz, p64_isPrintable ; If not, hop to testing to see if we can print this + ld de, (p64_coords) ; Get coords + call p64_nxtLine ; Go to next line. + + LOCAL p64_eaa3 + p64_eaa3: + ld (p64_coords), de + jr nextChar + + LOCAL p64_isPrintable + p64_isPrintable: + cp 31 ; Bigger than 31? + jr c, nextChar ; If not, get the next one. + push hl ; Save position + push bc ; Save Count + call p64_PrintChar ; Call Print SubRoutine + pop bc ; Recover length count + pop hl ; Recover Position + + LOCAL nextChar + nextChar: + inc hl ; Point to next character + dec bc ; Count off this character + ld a, b ; Did we run out? + or c + jr nz, examineChar ; If not, examine the next one + jp p64_END ; Otherwise hop to END. + + LOCAL p64_PrintChar + p64_PrintChar: + exx + push hl ; Save HL' + exx + sub 32 ; Take out 32 to convert ascii to position in charset + ld h, 0 + rra ; Divide by 2 + ld l, a ; Put our halved value into HL + ld a, 240 ; Set our mask to LEFT side + jr nc, p64_eacc ; If we didn't have a carry (even #), hop forward. + ld a, 15 ; If we were ab idd #, set our mask to RIGHT side instead + + LOCAL p64_eacc + p64_eacc: + add hl, hl + add hl, hl + add hl, hl ; Multiply our char number by 8 + ld de, p64_charset ; Get our Charset position + add hl, de ; And add our character count, so we're now pointed at the first + ; byte of the right character. + exx + ld de, (p64_coords) + ex af, af' + call p64_loadAndTest + ex af, af' + inc e + ld (p64_coords), de ; Put position+1 into coords + dec e + ld b, a + rr e ; Divide X position by 2 + ld c, 0 + rl c ; Bring carry flag into C (result of odd/even position) + and 1 ; Mask out lowest bit in A + xor c ; XOR with C (Matches position RightLeft with Char RightLeft) + ld c, a + jr z, p64_eaf6 ; If they are both the same, skip rotation. + ld a, b + rrca + rrca + rrca + rrca + ld b, a + + LOCAL p64_eaf6 + p64_eaf6: + ld a, d ; Get Y coord + sra a + sra a + sra a ; Multiply by 8 + p64_ATTR_HI: + add a, 88 ; Attribute area high byte (parcheado con .core.SCREEN_ATTR_ADDR+1 al entrar en print64) + ld h, a ; Put high byte value for attribute into H. + ld a, d + and 7 + rrca + rrca + rrca + add a, e + ld l, a ; Put low byte for attribute into l + ld a, (.core.ATTR_P) ; Get permanent Colours from our own sysvar + ld (hl), a ; Write new attribute + + ld a, d + and 248 + p64_SCR_HI: + add a, 64 ; Screen bitmap high byte (parcheado con .core.SCREEN_ADDR+1 al entrar en print64) + ld h, a + ld a, b + cpl + ld e, a + exx + ld b, 8 + + LOCAL p64_eb18 + p64_eb18: + ld a, (hl) + exx + bit 0, c + jr z, p64_eb22 + rrca + rrca + rrca + rrca + + LOCAL p64_eb22 + p64_eb22: + and b + ld d, a + ld a, (hl) + and e + or d + ld (hl), a + inc h + exx + inc hl + djnz p64_eb18 + exx + pop hl + exx + ret + + LOCAL p64_loadAndTest + p64_loadAndTest: + ld de, (p64_coords) + + ; SubRoutine to go to legal character position. + LOCAL p64_test_X + p64_test_X: + ld a, e ; Get column from e + cp 64 ; more than 64 ? + jr c, p64_test_Y ; If not, then jump over nextline + + LOCAL p64_nxtLine + p64_nxtLine: + inc d ; Move down 1 + ld e, 0 ; reset x co-ord to zero + + LOCAL p64_test_Y + p64_test_Y: + ld a, d ; get Y co-ord + cp 24 ; Past 24? + ret c ; Return if not. + ld d, 0 ; Rest y co-ord to top of screen. + ret ; Return. + end asm + p64coords: + asm + LOCAL p64_coords; + p64_coords: + defb 64; X Coordinate store + defb 23; Y Coordinate Store + + LOCAL p64_charset + p64_charset: + DEFB 0,2,2,2,2,0,2,0 ; Space ! + DEFB 0,80,82,7,2,7,2,0 ; "" # + DEFB 0,37,113,66,114,20,117,32 ; $ % + DEFB 0,34,84,32,96,80,96,0 ; & ' + DEFB 0,36,66,66,66,66,36,0 ; ( ) + DEFB 0,0,82,34,119,34,82,0 ; * + + DEFB 0,0,0,0,7,32,32,64 ; , - + DEFB 0,1,1,2,2,100,100,0 ; . / + DEFB 0,34,86,82,82,82,39,0 ; 0 1 + DEFB 0,34,85,18,33,69,114,0 ; 2 3 + DEFB 0,87,84,118,17,21,18,0 ; 4 5 + DEFB 0,55,65,97,82,84,36,0 ; 6 7 + DEFB 0,34,85,37,83,85,34,0 ; 8 9 + DEFB 0,0,2,32,0,34,2,4 ; : ; + DEFB 0,0,16,39,64,39,16,0 ; < = + DEFB 0,2,69,33,18,32,66,0 ; > ? + DEFB 0,98,149,183,181,133,101,0 ; @ A Changed from ;0,2,37,87,117,85,53,0 + DEFB 0,98,85,100,84,85,98,0 ; B C + DEFB 0,103,84,86,84,84,103,0 ; D E + DEFB 0,114,69,116,71,69,66,0 ; F G + DEFB 0,87,82,114,82,82,87,0 ; H I + DEFB 0,53,21,22,21,85,37,0 ; J K + DEFB 0,69,71,71,69,69,117,0 ; L M + DEFB 0,82,85,117,117,85,82,0 ; N O + DEFB 0,98,85,85,103,71,67,0 ; P Q + DEFB 0,98,85,82,97,85,82,0 ; R S + DEFB 0,117,37,37,37,37,34,0 ; T U + DEFB 0,85,85,85,87,39,37,0 ; V W + DEFB 0,85,85,37,82,82,82,0 ; X Y + DEFB 0,119,20,36,36,68,119,0 ; Z [ + DEFB 0,71,65,33,33,17,23,0 ; \ ] + DEFB 0,32,112,32,32,32,47,0 ; ^ _ + DEFB 0,32,86,65,99,69,115,0 ; £ a + DEFB 0,64,66,101,84,85,98,0 ; b c + DEFB 0,16,18,53,86,84,35,0 ; d e + DEFB 0,32,82,69,101,67,69,2 ; f g + DEFB 0,66,64,102,82,82,87,0 ; h i + DEFB 0,20,4,53,22,21,85,32 ; j k + DEFB 0,64,69,71,71,85,37,0 ; l m + DEFB 0,0,98,85,85,85,82,0 ; n o + DEFB 0,0,99,85,85,99,65,65 ; p q + DEFB 0,0,99,84,66,65,70,0 ; r s + DEFB 0,64,117,69,69,85,34,0 ; t u + DEFB 0,0,85,85,87,39,37,0 ; v w + DEFB 0,0,85,85,35,81,85,2 ; x y + DEFB 0,0,113,18,38,66,113,0 ; z { + DEFB 0,32,36,34,35,34,36,0 ; | { + DEFB 0,6,169,86,12,6,9,6 ; ~ (c) + +LOCAL p64_END + p64_END: + ENDP + end asm + + end sub + +#pragma pop(case_insensitive) + +#endif From 67f33150571aa05a182863a9a3eb1a35e0b40376 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 12:11:29 +0200 Subject: [PATCH 15/27] zx81sd: mover documentacion y herramientas a src/arch/zx81sd/ - docs/zx81sd/README.md -> src/arch/zx81sd/README.md - docs/zx81sd/{USO,PRECAUCIONES,CAMBIOS_BASIC,MAP}.md -> src/arch/zx81sd/doc/ - Copia del repositorio complementario (solo para pruebas, no fuente canonica) las herramientas de empaquetado a src/arch/zx81sd/tools/: split_sd81.py, zx81_p_loader.py, boot1.asm, boot1.bin. Enlaces relativos corregidos en toda la documentacion movida. Probado end-to-end (compilar + split_sd81.py) desde la nueva ubicacion. Co-Authored-By: Claude Sonnet 5 --- {docs => src/arch}/zx81sd/README.md | 66 ++- .../arch/zx81sd/doc}/CAMBIOS_BASIC.md | 0 {docs/zx81sd => src/arch/zx81sd/doc}/MAP.md | 6 +- .../arch/zx81sd/doc}/PRECAUCIONES.md | 2 +- {docs/zx81sd => src/arch/zx81sd/doc}/USO.md | 11 +- src/arch/zx81sd/tools/boot1.asm | 85 +++ src/arch/zx81sd/tools/boot1.bin | Bin 0 -> 27 bytes src/arch/zx81sd/tools/split_sd81.py | 205 +++++++ src/arch/zx81sd/tools/zx81_p_loader.py | 545 ++++++++++++++++++ 9 files changed, 880 insertions(+), 40 deletions(-) rename {docs => src/arch}/zx81sd/README.md (61%) rename {docs/zx81sd => src/arch/zx81sd/doc}/CAMBIOS_BASIC.md (100%) rename {docs/zx81sd => src/arch/zx81sd/doc}/MAP.md (99%) rename {docs/zx81sd => src/arch/zx81sd/doc}/PRECAUCIONES.md (99%) rename {docs/zx81sd => src/arch/zx81sd/doc}/USO.md (90%) create mode 100644 src/arch/zx81sd/tools/boot1.asm create mode 100644 src/arch/zx81sd/tools/boot1.bin create mode 100644 src/arch/zx81sd/tools/split_sd81.py create mode 100644 src/arch/zx81sd/tools/zx81_p_loader.py diff --git a/docs/zx81sd/README.md b/src/arch/zx81sd/README.md similarity index 61% rename from docs/zx81sd/README.md rename to src/arch/zx81sd/README.md index 9bf6ff056..890495505 100644 --- a/docs/zx81sd/README.md +++ b/src/arch/zx81sd/README.md @@ -8,7 +8,8 @@ páginas y acceso a tarjeta SD). Todo el código específico de esta arquitectura vive exclusivamente bajo: ``` -src/arch/zx81sd/ (backend del compilador) +src/arch/zx81sd/ (backend del compilador, esta documentación, + y las herramientas de empaquetado en tools/) src/lib/arch/zx81sd/ (runtime ASM + stdlib BASIC) ``` @@ -57,45 +58,50 @@ compartida: simulación (texto legible píxel a píxel, sin corrupción de memoria); pendiente de confirmar en el emulador/hardware real. -Ver [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md) para el patrón general de este -tipo de fix. El port de `maskedsprites.bas`/MSFS (sprites enmascarados -sobre el mapeador de memoria) también está en proceso, aún sin terminar -de funcionar bien. +Ver [doc/CAMBIOS_BASIC.md](doc/CAMBIOS_BASIC.md) para el patrón general +de este tipo de fix. El port de `maskedsprites.bas`/MSFS (sprites +enmascarados sobre el mapeador de memoria) también está en proceso, aún +sin terminar de funcionar bien. ## Documentación -- **[USO.md](USO.md)** — cómo compilar un programa, empaquetarlo para - el ZX81 y cargarlo desde la tarjeta SD. -- **[PRECAUCIONES.md](PRECAUCIONES.md)** — qué hay que tener en cuenta - al escribir o portar software para esta arquitectura (mapa de +- **[doc/USO.md](doc/USO.md)** — cómo compilar un programa, empaquetarlo + para el ZX81 y cargarlo desde la tarjeta SD. +- **[doc/PRECAUCIONES.md](doc/PRECAUCIONES.md)** — qué hay que tener en + cuenta al escribir o portar software para esta arquitectura (mapa de memoria, sysvars, teclado, cosas que NO existen aquí aunque existan en Spectrum). -- **[CAMBIOS_BASIC.md](CAMBIOS_BASIC.md)** — qué cambios de fuente - BASIC hizo falta para portar cada ejemplo oficial de `examples/` (con - el porqué de cada uno). Punto de partida obligado antes de portar un - ejemplo nuevo: casi siempre es uno de los patrones ya catalogados - ahí. -- **[MAP.md](MAP.md)** — bitácora técnica detallada de todos los bugs - encontrados y corregidos durante el port (runtime ASM, no fuentes - BASIC), con la traza de investigación de cada uno. Es el documento a - consultar cuando algo falla en tiempo de ejecución de forma que - recuerda a un bug ya resuelto. +- **[doc/CAMBIOS_BASIC.md](doc/CAMBIOS_BASIC.md)** — qué cambios de + fuente BASIC hizo falta para portar cada ejemplo oficial de + `examples/` (con el porqué de cada uno). Punto de partida obligado + antes de portar un ejemplo nuevo: casi siempre es uno de los patrones + ya catalogados ahí. +- **[doc/MAP.md](doc/MAP.md)** — bitácora técnica detallada de todos los + bugs encontrados y corregidos durante el port (runtime ASM, no + fuentes BASIC), con la traza de investigación de cada uno. Es el + documento a consultar cuando algo falla en tiempo de ejecución de + forma que recuerda a un bug ya resuelto. ## Ejemplos Los programas de ejemplo ya adaptados/probados para esta arquitectura -están en [`examples/sd81/`](../../examples/sd81/) (junto al resto de +están en [`examples/sd81/`](../../../examples/sd81/) (junto al resto de `examples/` del compilador). El detalle de qué hubo que tocar en cada -uno, y por qué, está en [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md). - -## Herramientas de empaquetado y pruebas - -El empaquetador (`split_sd81.py`, parte binario plano en páginas de -8KB + genera el cargador `.p` para el ZX81) y el conjunto más amplio de -fuentes de depuración/diagnóstico usados durante el desarrollo del port -viven en un repositorio complementario, no en este. Ver -[USO.md](USO.md) para el flujo completo de compilar → empaquetar → -cargar. +uno, y por qué, está en [doc/CAMBIOS_BASIC.md](doc/CAMBIOS_BASIC.md). + +## Herramientas de empaquetado + +[`tools/split_sd81.py`](tools/split_sd81.py) parte el binario plano +generado por el compilador en páginas de 8KB y genera el cargador `.p` +para el ZX81; [`tools/zx81_p_loader.py`](tools/zx81_p_loader.py) es el +tokenizador BASIC que usa para generarlo; [`tools/boot1.asm`](tools/boot1.asm)/ +[`tools/boot1.bin`](tools/boot1.bin) son el cargador de segunda etapa +(stage 1), fijo para todos los programas. Ver +[doc/USO.md](doc/USO.md) para el flujo completo de compilar → +empaquetar → cargar. El conjunto más amplio de fuentes de depuración/ +diagnóstico usados durante el desarrollo del port (no herramientas, +solo pruebas puntuales) vive en un repositorio complementario privado, +no en este. ## Repositorios relacionados diff --git a/docs/zx81sd/CAMBIOS_BASIC.md b/src/arch/zx81sd/doc/CAMBIOS_BASIC.md similarity index 100% rename from docs/zx81sd/CAMBIOS_BASIC.md rename to src/arch/zx81sd/doc/CAMBIOS_BASIC.md diff --git a/docs/zx81sd/MAP.md b/src/arch/zx81sd/doc/MAP.md similarity index 99% rename from docs/zx81sd/MAP.md rename to src/arch/zx81sd/doc/MAP.md index a6eec0504..2ffd4e9f1 100644 --- a/docs/zx81sd/MAP.md +++ b/src/arch/zx81sd/doc/MAP.md @@ -4,7 +4,7 @@ Nota sobre rutas: esta bitácora se escribió originalmente en el repositorio complementario de pruebas del port (donde los `.bas` citados vivían todos en un directorio `tests_debug/`). Los ejemplos que se consideraron suficientemente maduros para publicarse ya están copiados -en este repositorio, en [`examples/sd81/`](../../examples/sd81/) +en este repositorio, en [`examples/sd81/`](../../../../examples/sd81/) (`flights_sd81.bas`, `snake_sd81.bas`, `maskedsprites_sd81.bas`, `pong.bas`, `block7test.bas` — ver [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md)). El resto de fuentes de depuración puntual mencionadas aquí (`diag1-6`, @@ -14,9 +14,9 @@ repositorio complementario, no en este. Compilación de cada uno (desde la raíz de este repositorio): ``` python -m src.zxbc.zxbc examples\sd81\.bas --arch zx81sd -o .bin -python split_sd81.py .bin +python src\arch\zx81sd\tools\split_sd81.py .bin ``` -(`split_sd81.py` vive en el repositorio complementario — ver [USO.md](USO.md).) +(ver [USO.md](USO.md) para el detalle del empaquetador.) | Fuente | Prefijo SD81 | Qué prueba | Resultado obtenido | |------------------------|--------------|---------------------------------------------------------------------|---------------------| diff --git a/docs/zx81sd/PRECAUCIONES.md b/src/arch/zx81sd/doc/PRECAUCIONES.md similarity index 99% rename from docs/zx81sd/PRECAUCIONES.md rename to src/arch/zx81sd/doc/PRECAUCIONES.md index a83c15551..8c22fb451 100644 --- a/docs/zx81sd/PRECAUCIONES.md +++ b/src/arch/zx81sd/doc/PRECAUCIONES.md @@ -22,7 +22,7 @@ enteras en diagnosticarse por esto). - **Sysvars del Spectrum → sysvars de zx81sd**: la tabla de equivalencias está en - [`../../src/lib/arch/zx81sd/runtime/sysvars.asm`](../../src/lib/arch/zx81sd/runtime/sysvars.asm) + [`../../../lib/arch/zx81sd/runtime/sysvars.asm`](../../../lib/arch/zx81sd/runtime/sysvars.asm) (todas viven en `$8000+`, no en `$5C00+`). Ejemplos ya resueltos: `UDG` (23675 → `$8002`), `COORDS` (23677/23678 → `$8004`/`$8005`). Ver [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md) para el detalle línea a diff --git a/docs/zx81sd/USO.md b/src/arch/zx81sd/doc/USO.md similarity index 90% rename from docs/zx81sd/USO.md rename to src/arch/zx81sd/doc/USO.md index 25438cfd3..6ed141ddc 100644 --- a/docs/zx81sd/USO.md +++ b/src/arch/zx81sd/doc/USO.md @@ -9,7 +9,7 @@ python -m src.zxbc.zxbc --arch zx81sd -o -M ` es opcional pero muy recomendable: genera el mapa de símbolos (dirección de cada label ASM/BASIC), imprescindible para depurar con un emulador o simular el binario (ver [MAP.md](MAP.md) @@ -30,12 +30,10 @@ en páginas de 8KB y generar un cargador BASIC que las vaya metiendo en la tarjeta SD81 Booster una a una, remapeando el mapeador de memoria entre página y página. -Esto lo hace `split_sd81.py`, una herramienta que vive en el -repositorio complementario de pruebas del port (no en este -repositorio del compilador): +Esto lo hace [`../tools/split_sd81.py`](../tools/split_sd81.py): ``` -python split_sd81.py [PREFIJO] +python src/arch/zx81sd/tools/split_sd81.py [PREFIJO] ``` - `PREFIJO` (opcional) es el nombre base de los ficheros de salida, en @@ -54,7 +52,8 @@ El cargador generado usa `LOAD THEN CLEAR`, `LOAD *MAP` y existen en la ROM original del ZX81). Hace lo siguiente, en orden: 1. Reserva memoria (`CLEAR`) y carga `BOOT1.BIN` (el stage 1, fijo para - todos los programas, también en el repositorio complementario). + todos los programas — fuente en [`../tools/boot1.asm`](../tools/boot1.asm), + binario ya ensamblado en [`../tools/boot1.bin`](../tools/boot1.bin)). 2. Por cada página del binario: mapea el bloque 7 a esa página física (`LOAD *MAP 7,`) y vuelca los datos ahí (`LOAD FAST ... CODE 57344`, la ventana del bloque 7 en `$E000`). diff --git a/src/arch/zx81sd/tools/boot1.asm b/src/arch/zx81sd/tools/boot1.asm new file mode 100644 index 000000000..16217c4de --- /dev/null +++ b/src/arch/zx81sd/tools/boot1.asm @@ -0,0 +1,85 @@ +; =========================================================================== +; bootstrap_stage1.asm — Stage 1 Bootstrap ZX81 + SD81 Booster +; +; Reside en $6000 (bloque 3). El cargador BASIC lo envía allí antes de +; nada porque es una zona neutral: BASIC del ZX81 no la necesita, y aún +; no hemos remapeado ningún bloque. +; +; Secuencia de inicio (ver ZX81-SD81-Adaptation-Plan.md): +; 1. DI — deshabilitar interrupciones (el ZX81 FAST mode ya lo hace, pero +; lo repetimos por seguridad antes de tocar la paginación) +; 2. Activar modo Superfast HiRes Spectrum vía FPGA (POKE 2045 = $07FD) +; 3. Desactivar IO mapeado en memoria (POKE 2056 = $0808) +; 4. Mapear bloque 0 → página 8 (OUT ($E7), page=8, block=0) +; El stage 2 ($0100-$0FFF en página 8) está ahora listo para ejecutar. +; 5. JP $0100 — salta al stage 2 en la RAM recién mapeada +; +; Notas: +; - HFILE=$C000 es el valor por defecto de la FPGA al activar modo 172. +; Si tu hardware requiere configurarlo explícitamente, descomenta el +; bloque HFILE al final. +; - El stage 1 NO inicializa SP; el stage 2 lo hace (ld sp, $7FFF). +; - Bloques 1-5 se mapean en el stage 2 (ya ejecutando desde página 8). +; +; Compilar: +; zxbasm bootstrap_stage1.asm -o bootstrap_stage1.bin +; o con pasmo: +; pasmo --bin bootstrap_stage1.asm bootstrap_stage1.bin +; +; Cargar en el emulador / hardware a dirección $6000 (24576 decimal). +; Ejecutar desde BASIC con: +; RANDOMIZE USR 24576 +; =========================================================================== + + org $6000 + +SD81_STAGE1: + + di ; Interrupciones desactivadas + + ; -------------------------------------------------------------------------- + ; Asignar la direccion del framebuffer (HFILE) + ; high=2044 low=(2043) + ; + + ld hl, $C000 + ld ($07FB), hl + + ; -------------------------------------------------------------------------- + ; ------------------------------------------------------------------ + ; Activar modo Superfast HiRes Spectrum + ; POKE 2045, 172 → ld a, 172 / ld ($07FD), a + ; La FPGA del SD81 Booster interpreta esto como: + ; modo 172 ($AC) = Spectrum 256x192 desde HFILE=$C000 + ; ------------------------------------------------------------------ + ld a, 172 + ld ($07FD), a + + ; ------------------------------------------------------------------ + ; Desactivar IO mapeado en memoria + ; POKE 2056, 0 → ld ($0808), a + ; Evita colisiones entre las instrucciones IN/OUT y el espacio de RAM + ; ------------------------------------------------------------------ + xor a + ld ($0808), a + + ; ------------------------------------------------------------------ + ; Mapear bloque 0 ($0000-$1FFF) → página 8 del SD81 + ; Puerto $E7: modo full 64 páginas, OUT (C), A con B=página, A=bloque + ; La página 8 contiene el binario compilado (vectors + stage 2 + runtime) + ; ------------------------------------------------------------------ + ld b, 8 ; página SD81 destino (8 = primera página de usuario) + ld a, 0 ; bloque Z80 a reasignar (bloque 0 = $0000-$1FFF) + ld c, $E7 ; puerto de paginación SD81 + out (c), a ; mapear (B=página, A=bloque) + + ; ------------------------------------------------------------------ + ; Saltar al stage 2 en la RAM recién mapeada + ; A partir de aquí el bloque 0 contiene la página 8: + ; $0000-$0067 vectores RST + ; $0100 __START_PROGRAM (inicio del stage 2) + ; ------------------------------------------------------------------ + jp $0100 + + + end SD81_STAGE1 diff --git a/src/arch/zx81sd/tools/boot1.bin b/src/arch/zx81sd/tools/boot1.bin new file mode 100644 index 0000000000000000000000000000000000000000..0fc567529aa295b6cf20ef3bc636ee3ce75b37f6 GIT binary patch literal 27 jcmeyY$Z$aEH@n>$qrdFyjW{^iIP4hsp1-X;%)kf$jm8Oe literal 0 HcmV?d00001 diff --git a/src/arch/zx81sd/tools/split_sd81.py b/src/arch/zx81sd/tools/split_sd81.py new file mode 100644 index 000000000..5dfe7db79 --- /dev/null +++ b/src/arch/zx81sd/tools/split_sd81.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +""" +split_sd81.py — Particionador de binario ZX81+SD81 Booster + generador de loader + +Divide el binario plano generado por zxbc.py (--arch zx81sd, -f bin) +en páginas de 8KB para su carga desde el cargador BASIC del ZX81, y genera +el listado BASIC (texto plano, no tokenizado) necesario para cargarlas. + +Mapa de páginas: + Página 8 → bloque 0 ($0000-$1FFF): vectors + stage2 + runtime + código usuario + Página 9 → bloque 1 ($2000-$3FFF): continuación si el binario supera 8KB + Página 10 → bloque 2 ($4000-$5FFF): ídem + Página 11 → bloque 3 ($6000-$7FFF): ídem (stage 1 externo reside aquí antes del salto) + Página 12 → bloque 4 ($8000-$9FFF): sysvars + heap (datos, no ejecutable sin MC45) + Página 13 → bloque 5 ($A000-$BFFF): heap continuación + +El binario empieza en $0000 y no incluye cabecera .tap/.tzx. +Cada fichero de salida se llama P.BIN (mayúsculas) donde N es la página. + +El loader BASIC generado (texto plano, aún no tokenizado a .p) sigue la +secuencia validada manualmente sobre hardware/emulador: + + 2 FAST + 5 LOAD THEN CLEAR 24575 + 10 LOAD FAST "BOOT1.BIN"CODE 24576 + 20 LOAD *MAP 7,8 + 25 LOAD FAST "P8.BIN"CODE 57344 + 30 LOAD *MAP 7,9 + 35 LOAD FAST "P9.BIN"CODE 57344 + ... + LOAD *MAP 7,63 + RAND USR 24576 + +Nota: "LOAD THEN CLEAR", "LOAD *MAP" y "LOAD FAST ... CODE" son extensiones +propias del firmware del SD81 Booster sobre el BASIC del ZX81 (no existen en +la ROM original). El bloque 7 ($E000/57344) se usa como ventana de paginación +temporal para volcar cada página física antes de que BOOT1.BIN haga el mapeo +definitivo de bloques 0-5 vía el puerto $E7. El "LOAD *MAP 7,63" final fuerza +el cambio a modo full-paging (64 páginas) y deja el bloque 7 en un estado +neutro antes de saltar al programa. + +Además del listado en texto plano, genera el .p tokenizado real (listo para +cargar/ejecutar) usando zx81_p_loader.py, un puerto a Python del tokenizador +de EightyOne (zx81BasicLoader.cpp / IBasicLoader.cpp). + +Uso: + python split_sd81.py [salida_base] + + Si no se especifica salida_base, se usa el nombre de entrada sin extensión. + +Ejemplo: + python split_sd81.py test_sd81.bin + → TEST_SD81P8.BIN (primera página, siempre presente) + → TEST_SD81P9.BIN (solo si el binario supera 8 KB) + → ... + → test_sd81_loader.txt (listado BASIC del cargador, texto plano) + → TEST_SD81.P (mismo listado, tokenizado) +""" + +import sys +import os + +from zx81_p_loader import build_p_file + +PAGE_SIZE = 8192 # 8KB por página +FIRST_PAGE = 8 # página SD81 asignada al bloque 0 +CLEANUP_PAGE = 63 # página neutra: fuerza modo full-paging (64 páginas) +BOOT1_FILE = "BOOT1.BIN" +BOOT1_ADDR = 24576 # $6000 +CLEAR_ADDR = 24575 # protege BOOT1.BIN (reservado justo debajo) +LOAD_WINDOW_ADDR = 57344 # $E000, ventana de bloque 7 + + +def split(input_path: str, output_base: str) -> list[str]: + with open(input_path, "rb") as f: + data = f.read() + + if not data: + sys.exit(f"Error: {input_path} está vacío") + + pages_written = [] + page_num = FIRST_PAGE + offset = 0 + + while offset < len(data): + chunk = data[offset : offset + PAGE_SIZE] + + # Rellenar hasta PAGE_SIZE con 0xFF (valor indefinido de FLASH/RAM sin inicializar) + if len(chunk) < PAGE_SIZE: + chunk = chunk + b"\xff" * (PAGE_SIZE - len(chunk)) + + out_path = f"{output_base}P{page_num}.BIN" + with open(out_path, "wb") as f: + f.write(chunk) + + pages_written.append(out_path) + print( + f" Pagina {page_num} (bloque {page_num - FIRST_PAGE}): " + f"{out_path} " + f"[{offset:#06x} - {min(offset + PAGE_SIZE - 1, len(data) - 1):#06x}]" + ) + + offset += PAGE_SIZE + page_num += 1 + + return pages_written + + +def generate_loader_lines(page_files: list[str]) -> list[tuple[int, str]]: + lines = [] + lines.append((2, "FAST")) + lines.append((5, f"LOAD THEN CLEAR {CLEAR_ADDR}")) + lines.append((10, f'LOAD FAST "{BOOT1_FILE}"CODE {BOOT1_ADDR}')) + + line_no = 20 + for i, page_file in enumerate(page_files): + page_num = FIRST_PAGE + i + lines.append((line_no, f"LOAD *MAP 7,{page_num}")) + lines.append((line_no + 5, f'LOAD FAST "{page_file}"CODE {LOAD_WINDOW_ADDR}')) + line_no += 10 + + lines.append((line_no, f"LOAD *MAP 7,{CLEANUP_PAGE}")) + lines.append((line_no + 10, f"RAND USR {BOOT1_ADDR}")) + + return lines + + +def generate_loader_text(lines: list[tuple[int, str]]) -> str: + return "\n".join(f"{n} {cmd}" for n, cmd in lines) + "\n" + + +def main(): + if len(sys.argv) < 2: + print(__doc__) + sys.exit(1) + + input_path = sys.argv[1] + output_base = sys.argv[2] if len(sys.argv) > 2 else os.path.splitext(input_path)[0] + output_base = os.path.basename(output_base).upper() + + # Los nombres de fichero de las paginas y del loader.p se escriben en el + # charset del ZX81 (letras, digitos y unos pocos simbolos: sin guion + # bajo ni otros caracteres no representables). Fallar pronto y claro en + # vez de que reviente mas tarde al tokenizar el loader. + from zx81_p_loader import ascii_to_zx + for c in output_base: + try: + ascii_to_zx(c) + except ValueError: + sys.exit( + f"Error: el nombre base {output_base!r} contiene {c!r}, " + "no representable en el charset ZX81 (evita '_' y similares)." + ) + + if not os.path.isfile(input_path): + sys.exit(f"Error: no se encuentra {input_path!r}") + + size = os.path.getsize(input_path) + num_pages = (size + PAGE_SIZE - 1) // PAGE_SIZE + print(f"Binario: {input_path} ({size} bytes, {size/1024:.1f} KB, {num_pages} pagina/s)") + print(f"Particionando en paginas de {PAGE_SIZE} bytes (pagina SD81 inicial = {FIRST_PAGE})...") + print() + + pages = split(input_path, output_base) + + print() + print(f"Generados {len(pages)} fichero/s.") + if len(pages) == 1: + print("El programa cabe en una sola pagina (8 KB).") + else: + print(f"Paginas {FIRST_PAGE} a {FIRST_PAGE + len(pages) - 1}.") + + loader_lines = generate_loader_lines(pages) + loader_text = generate_loader_text(loader_lines) + + # El .p es un nombre de fichero real para el ZX81 (charset sin guion + # bajo): se deriva de output_base (mismo prefijo que las paginas), + # concatenado sin separador, igual que "P8.BIN". + base_no_ext = os.path.splitext(input_path)[0] + loader_txt_path = f"{base_no_ext}_loader.txt" + loader_p_path = f"{output_base}.P" + + with open(loader_txt_path, "w", newline="\n") as f: + f.write(loader_text) + + p_data = build_p_file(loader_lines) + with open(loader_p_path, "wb") as f: + f.write(p_data) + + print() + print(f"Loader BASIC (texto plano): {loader_txt_path}") + print("---") + print(loader_text, end="") + print("---") + print(f"Loader BASIC tokenizado (.p): {loader_p_path} ({len(p_data)} bytes)") + print() + print("NOTA: 'LOAD THEN CLEAR', 'LOAD *MAP' y 'LOAD FAST ... CODE' son extensiones") + print("del firmware SD81 Booster, codificadas con el mismo mecanismo de tokens que") + print("el BASIC estandar del ZX81 (ver zx81_p_loader.py).") + print(f"Copia {BOOT1_FILE}, los ficheros de pagina y {os.path.basename(loader_p_path)}") + print("a la tarjeta SD.") + + +if __name__ == "__main__": + main() diff --git a/src/arch/zx81sd/tools/zx81_p_loader.py b/src/arch/zx81sd/tools/zx81_p_loader.py new file mode 100644 index 000000000..75dfb166d --- /dev/null +++ b/src/arch/zx81sd/tools/zx81_p_loader.py @@ -0,0 +1,545 @@ +#!/usr/bin/env python3 +""" +zx81_p_loader.py — Generador de ficheros .p (BASIC tokenizado) para ZX81 + +Puerto directo a Python del algoritmo de tokenización usado por el emulador +EightyOne (zx81BasicLoader.cpp / IBasicLoader.cpp), referencia: + C:\\ClaudeCode\\Eightyone2\\src\\zx81\\zx81BasicLoader.cpp + C:\\ClaudeCode\\Eightyone2\\src\\BasicLoader\\IBasicLoader.cpp + +Los comandos extendidos del firmware SD81 Booster (LOAD THEN CLEAR, LOAD *MAP, +LOAD FAST ... CODE, etc.) no necesitan tratamiento especial: aunque esa +sintaxis no la admite el intérprete de la ROM del ZX81, están compuestos por: + - Tokens estándar del ZX81 (THEN, CLEAR, FAST, CODE, LOAD...) en posiciones + que la ROM no usaría pero que SÍ se pueden teclear (p.ej. SHIFT+3 = THEN). + - Palabras sueltas tras un asterisco (p.ej. *MAP, *VER) que son letras + corrientes, no tokens. +Por tanto basta con tokenizar el texto igual que cualquier BASIC estándar: +el propio algoritmo genérico ya produce la codificación correcta. + +No implementa (no hace falta para este proyecto): etiquetas @label, bloques +numéricos [DEC:...]/[HEX:...]/[BIN:...], secuencias de escape \\xx, códigos +gráficos, ni "alternate keyword spelling". Documentados en IBasicLoader.cpp +si en el futuro hicieran falta. +""" + +import math +import re +import struct + +BLANK = "\x01" # marcador interno: posición ya consumida +NEWLINE = 0x76 +NUMBER_MARK = 0x7E # precede a los 5 bytes de número embebido +DOUBLE_QUOTE = 0xC0 # comilla escapada ("") dentro de una cadena + +# Tokens estándar ZX81 (de zx81BasicLoader::ExtractTokens, sin variantes +# "alternate spelling" ni extensiones ZXpand) +TOKENS = { + 64: "RND", + 65: "INKEY$", + 66: "PI", + 193: "AT ", + 194: "TAB ", + 196: "CODE ", + 197: "VAL ", + 198: "LEN ", + 199: "SIN ", + 200: "COS ", + 201: "TAN ", + 202: "ASN ", + 203: "ACS ", + 204: "ATN ", + 205: "LN ", + 206: "EXP ", + 207: "INT ", + 208: "SQR ", + 209: "SGN ", + 210: "ABS ", + 211: "PEEK ", + 212: "USR ", + 213: "STR$ ", + 214: "CHR$ ", + 215: "NOT ", + 216: "**", + 217: " OR ", + 218: " AND ", + 219: "<=", + 220: ">=", + 221: "<>", + 222: " THEN ", + 223: " TO ", + 224: " STEP ", + 225: " LPRINT ", + 226: " LLIST ", + 227: " STOP ", + 228: " SLOW ", + 229: " FAST ", + 230: " NEW ", + 231: " SCROLL ", + 232: " CONT ", + 233: " DIM ", + 234: " REM ", + 235: " FOR ", + 236: " GOTO ", + 237: " GOSUB ", + 238: " INPUT ", + 239: " LOAD ", + 240: " LIST ", + 241: " LET ", + 242: " PAUSE ", + 243: " NEXT ", + 244: " POKE ", + 245: " PRINT ", + 246: " PLOT ", + 247: " RUN ", + 248: " SAVE ", + 249: " RAND ", + 250: " IF ", + 251: " CLS ", + 252: " UNPLOT ", + 253: " CLEAR ", + 254: " RETURN ", + 255: " COPY ", +} + + +def ascii_to_zx(c: str) -> int: + """Puerto de zx81BasicLoader::AsciiToZX.""" + if c.isalpha(): + return (ord(c.upper()) - ord('A')) + 38 + if c.isdigit(): + return (ord(c) - ord('0')) + 28 + + table = { + ' ': 0, '"': 11, '#': 12, '$': 13, ':': 14, '?': 15, + '(': 16, ')': 17, '-': 22, '+': 21, '*': 23, '/': 24, + '=': 20, '>': 18, '<': 19, ';': 25, ',': 26, '.': 27, + } + if c in table: + return table[c] + + raise ValueError(f"Caracter invalido: {c!r}") + + +def zx81_float(value: float) -> bytes: + """Puerto de zx81BasicLoader::OutputFloatingPointEncoding.""" + exponent = 0 + mantissa = 0 + + if value != 0: + neg = value < 0 + if neg: + value = -value + + exponent = int(math.floor(1e-12 + (math.log(value) / math.log(2.0)))) + if exponent < -129 or exponent > 126: + raise OverflowError("Number out of range") + + mantissa_val = (value / (2.0 ** exponent)) - 1 + mantissa_val *= 0x80000000 + mantissa = int(math.floor(mantissa_val)) + + exponent += 129 + + return bytes([ + exponent & 0xFF, + (mantissa >> 24) & 0xFF, + (mantissa >> 16) & 0xFF, + (mantissa >> 8) & 0xFF, + mantissa & 0xFF, + ]) + + +class _Line: + """Estado de trabajo para una linea BASIC (sin numero de linea), replica + los buffers mLineBuffer / mLineBufferOutput / mLineBufferPopulated. + + Todos los arrays tienen longitud self.n + 1: el ultimo hueco (indice n) + es un relleno de seguridad para cuando un token consume el espacio + artificial añadido al final para detectar tokens sin espacio de cierre + en el texto original (igual que el buffer sobredimensionado de C++). + """ + + def __init__(self, text: str): + content = " " + text # ReadLine antepone siempre un espacio + self.n = len(content) + self.buf = list(content) + [BLANK] # +1 hueco de seguridad + self.out = [BLANK] * (self.n + 1) + self.populated = [False] * (self.n + 1) + # BlankLineStart: sin numero de linea embebido, deja 1 espacio real + self.buf[0] = ' ' + + +def _mask_copy(chars: list) -> list: + return list(chars) + + +def _mask_out_strings(tokenised: list): + """Puerto de IBasicLoader::MaskOutStrings.""" + text = "".join(tokenised) + q1 = text.find('"') + if q1 == -1: + return + rem = " REM " + r1 = text.find(rem) + if r1 != -1 and r1 < q1: + return + + within = False + i = q1 + n = len(tokenised) + while i < n: + if tokenised[i] == '"': + within = not within + elif within: + tokenised[i] = BLANK + else: + rest = "".join(tokenised[i + 1:]) + nq = rest.find('"') + if nq == -1: + return + nr = rest.find(rem) + if nr != -1 and nr < nq: + return + i += 1 + + +def _mask_out_rem_contents(tokenised: list): + """Puerto de IBasicLoader::MaskOutRemContents.""" + text = "".join(tokenised) + rem = " REM " + pos = 0 + while True: + r = text.find(rem, pos) + if r == -1: + return + q = text.find('"', pos) + if q != -1 and q < r: + q2 = text.find('"', q + 1) + if q2 == -1: + return + pos = q2 + 1 + continue + start = r + len(rem) + for i in range(start, len(tokenised)): + tokenised[i] = BLANK + return + + +def _extract_double_quote_characters(line: _Line): + """Puerto de zx81BasicLoader::ExtractDoubleQuoteCharacters (sin soporte + de REM tokenizado ni alternate spelling, no necesarios aqui).""" + buf = line.buf + n = line.n + within_quote = False + i = 0 + while i < n: + if not within_quote: + if buf[i] == '"': + within_quote = True + else: + chr1 = buf[i] + if chr1 == '"': + if i + 1 < n and buf[i + 1] == '"': + buf[i] = BLANK + line.out[i] = DOUBLE_QUOTE + line.populated[i] = True + i += 1 + buf[i] = BLANK + else: + within_quote = False + i += 1 + + +def _do_tokenise(line: _Line, tokenised: list): + """Puerto de IBasicLoader::DoTokenise. Recorre los tokens de codigo mas + alto a mas bajo (equivalente al reverse_iterator sobre un std::map), + mutando `tokenised` (busqueda) y line.buf/out/populated (resultado).""" + for token_code in sorted(TOKENS.keys(), reverse=True): + token = TOKENS[token_code] + len_token = len(token) + + start_char = token[0] + end_char = token[-1] + + token_begins_with_space = start_char == ' ' + token_begins_with_alpha = start_char.isalpha() + token_ends_with_space = end_char == ' ' + token_ends_with_alpha = end_char.isalpha() + + len_adjustment = 0 + eff_len_token = len_token + if end_char in ('(', ')', '!', '"', "'", ',', ';', ':') or \ + (end_char == '#' and (len_token < 2 or token[-2] != ' ')) or \ + (end_char == '*' and token != "**"): + eff_len_token -= 1 + + guard = 0 + while True: + guard += 1 + if guard > 10000: + raise RuntimeError(f"Bucle de tokenizacion sin fin para {token!r}") + + text = "".join(tokenised) + pos = text.find(token) + if pos == -1: + break + + prev_ok = (token_begins_with_space or not token_begins_with_alpha + or (pos == 0) or (not tokenised[pos - 1].isalnum())) + end_pos = pos + eff_len_token + next_ok = (token_ends_with_space or not token_ends_with_alpha + or (end_pos >= len(tokenised)) or (not tokenised[end_pos].isalnum())) + + if not (prev_ok and next_ok): + # Coincidencia pegada a un identificador: no es el token, + # se "rompe" localmente para seguir buscando mas adelante. + tokenised[pos] = '\x02' + continue + + start_offset = 1 if token_begins_with_space else 0 + end_offset = (eff_len_token - 1) if token_ends_with_space else (eff_len_token + len_adjustment) + + for b in range(start_offset, end_offset): + tokenised[pos + b] = BLANK + + for b in range(eff_len_token + len_adjustment): + idx = pos + b + if idx < len(line.buf): + line.buf[idx] = BLANK + + output_index = pos + start_offset + if output_index < len(line.out): + line.out[output_index] = token_code + line.populated[output_index] = True + + +def _start_of_number(line: _Line, tokenised: list, index: int) -> bool: + """Determina si `index` es el inicio de un literal numerico. + + Difiere deliberadamente del original IBasicLoader::StartOfNumber en un + punto: aqui solo se mira el caracter INMEDIATAMENTE anterior (retrocede + sobre espacios reales pero se detiene tan pronto encuentra CUALQUIER + caracter que no sea espacio, sin seguir retrocediendo mas alla). El + original retrocedia sobre TODOS los espacios consecutivos y comprobaba + el caracter previo a todos ellos, lo cual en BASIC estandar es + equivalente (los tokens siempre consumen su propio espacio final como + BLANK, no como ' ' literal) pero falla en comandos custom del SD81 como + "*MAP 7,8": "MAP" no es un token reconocido, por lo que el espacio tras + el queda como ' ' literal, y el algoritmo original terminaba mirando la + 'P' de MAP (alfabetica) y decidia que "7" NO era un numero. En un ZX81 + real, cualquier digito tecleado tras un espacio genera su propio float + embebido sin importar que palabra le precede, asi que aqui se replica + ese comportamiento (mas simple y mas correcto para este caso). + """ + if not (line.buf[index] == '.' or line.buf[index].isdigit()): + return False + + if index == 0: + return True + + prev = line.buf[index - 1] + if prev == ' ': + return True + + return not (prev.isalpha() or prev.isdigit()) + + +_NUMBER_RE = re.compile(r'[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?') + + +def _output_embedded_number(line: _Line, index: int, out: bytearray) -> int: + """Puerto de IBasicLoader::OutputEmbeddedNumber. Devuelve el nuevo + indice (ya avanzado mas alla del numero, listo para `i += 1` fuera).""" + n = line.n + + text_no_spaces = "" + for j in range(index, n): + if line.buf[j] != BLANK: + text_no_spaces += line.buf[j] + + m = _NUMBER_RE.match(text_no_spaces) + if not m or m.end() == 0: + raise ValueError(f"Numero invalido en posicion {index}: {text_no_spaces!r}") + number_len_no_spaces = m.end() + value = float(m.group(0)) + + with_spaces_index = 0 + for _ in range(number_len_no_spaces): + while line.buf[index + with_spaces_index] in (' ', BLANK): + with_spaces_index += 1 + with_spaces_index += 1 + + end_index = index + with_spaces_index + + i = index + while i < end_index: + chr_code = line.out[i] + if chr_code != BLANK and line.populated[i]: + out.append(chr_code) + i += 1 + + while i < n and line.buf[i] == ' ': + out.append(ascii_to_zx(' ')) + i += 1 + + out.append(NUMBER_MARK) + out.extend(zx81_float(value)) + + return i - 1 + + +def process_line_body(text: str) -> bytes: + """Tokeniza una linea BASIC (sin numero de linea) y devuelve los bytes + ya codificados (sin NEWLINE final, que se añade en encode_line).""" + line = _Line(text) + n = line.n + + _extract_double_quote_characters(line) + + # Copia de trabajo para deteccion de limites de token: el contenido real + # (sin el hueco de seguridad de line.buf) + un espacio real extra para + # detectar tokens sin espacio de cierre en el texto original. Longitud + # n+1, igual que line.buf, para que los indices sigan alineados. + tokenised = list(line.buf[:n]) + [' '] + + _mask_out_strings(tokenised) + _mask_out_rem_contents(tokenised) + + _do_tokenise(line, tokenised) + + # Rellenar cualquier posicion no consumida con su codigo ZX81 directo + for i in range(1, n): + if line.buf[i] != BLANK and not line.populated[i]: + line.out[i] = ascii_to_zx(line.buf[i]) + line.populated[i] = True + + body = bytearray() + i = 0 + while i < n: + if line.populated[i]: + if _start_of_number(line, tokenised, i): + i = _output_embedded_number(line, i, body) + 1 + continue + body.append(line.out[i]) + i += 1 + + return bytes(body) + + +def encode_line(line_number: int, text: str) -> bytes: + body = process_line_body(text) + bytes([NEWLINE]) + return struct.pack('>H', line_number) + struct.pack(' bytes: + """lines: lista de tuplas (numero_de_linea:int, texto:str).""" + return b"".join(encode_line(n, t) for n, t in lines) + + +def build_p_file(lines: list) -> bytes: + """Puerto de zx81BasicLoader::OutputStartOfProgramData + + ProcessLine(*) + OutputEndOfProgramData. Devuelve el contenido completo + del fichero .p (desde 0x4009).""" + data = bytearray() + + def out_byte(b): + data.append(b & 0xFF) + + def out_word(w): + data.append(w & 0xFF) + data.append((w >> 8) & 0xFF) + + def change_word(offset, w): + data[offset] = w & 0xFF + data[offset + 1] = (w >> 8) & 0xFF + + # --- OutputStartOfProgramData --- + out_byte(0x00) # VERSN + out_word(0x0000) # E_PPC + out_word(0x0000) # D_FILE + out_word(0x0000) # DF_CC + out_word(0x0000) # VARS + out_word(0x0000) # DEST + out_word(0x0000) # E_LINE + out_word(0x0000) # CH_ADD + out_word(0x0000) # X_PTR + out_word(0x0000) # STKBOT + out_word(0x0000) # STKEND + out_byte(0x00) # BERG + out_word(0x405D) # MEM + out_byte(0x00) # SPARE1 + out_byte(0x02) # DF_SZ + out_word(0x0000) # S_TOP + out_word(0xFFFF) # LAST_K + out_byte(0x00) # DBOUNC + out_byte(0x37) # MARGIN + out_word(0x0000) # NXTLIN + out_word(0x0000) # OLDPPC + out_byte(0x00) # FLAGX + out_word(0x0000) # STRLEN + out_word(0x0C8D) # T_ADDR + out_word(0x4321) # SEED + out_word(0xE6E0) # FRAMES + out_word(0x0000) # COORDS + out_byte(0xBC) # PR_CC + out_word(0x1821) # S_POSN + out_byte(0x40) # CDFLAG + data.extend([0x00] * 32) # PRBUFF (32 bytes vacios) + out_byte(0x76) # PRBUFF (byte 33, NEWLINE) + data.extend([0x00] * 30) # MEMBOT + out_word(0x0000) # SPARE + + # --- Lineas BASIC --- + for line_number, text in lines: + data.extend(encode_line(line_number, text)) + + # --- OutputEndOfProgramData --- + START_OF_RAM = 16393 # 0x4009 + + dfile_address = START_OF_RAM + len(data) + data.extend([NEWLINE] * 25) # display file colapsado (vacio) + + vars_address = START_OF_RAM + len(data) + out_byte(0x80) # fin de VARS (sin variables) + + eline_address = START_OF_RAM + len(data) + + change_word(3, dfile_address) # D_FILE + change_word(5, dfile_address + 1) # DF_CC + change_word(7, vars_address) # VARS + change_word(11, vars_address + 1) # E_LINE + change_word(13, vars_address + 5) # CH_ADD + change_word(17, eline_address + 5) # STKBOT + change_word(19, eline_address + 5) # STKEND + change_word(32, vars_address) # NXTLIN (= VARS => sin autorun) + + return bytes(data) + + +if __name__ == "__main__": + import sys + + # Autotest rapido con el loader validado manualmente + lines = [ + (2, "FAST"), + (5, "LOAD THEN CLEAR 24575"), + (10, 'LOAD FAST "BOOT1.BIN"CODE 24576'), + (20, "LOAD *MAP 7,8"), + (25, 'LOAD FAST "TESTSD81P8.BIN"CODE 57344'), + (30, "LOAD *MAP 7,9"), + (35, 'LOAD FAST "TESTSD81P9.BIN"CODE 57344'), + (50, "LOAD *MAP 7,63"), + (60, "RAND USR 24576"), + ] + + p_data = build_p_file(lines) + out_path = sys.argv[1] if len(sys.argv) > 1 else "loader_test.p" + with open(out_path, "wb") as f: + f.write(p_data) + + print(f"Generado {out_path} ({len(p_data)} bytes)") + for i in range(0, len(p_data), 16): + chunk = p_data[i:i + 16] + hexpart = " ".join(f"{b:02X}" for b in chunk) + print(f"{i:04X}: {hexpart}") From 418dd1ffd86cb4303321894f2394f09e822f59fd Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 12:43:52 +0200 Subject: [PATCH 16/27] zx81sd: fix loading instructions and translate documentation to English Fix: the packaged program is loaded with LOAD FAST "" (nothing to select afterwards), not LOAD "" + select. Corrected in USO.md (now USAGE.md) and split_sd81.py's docstring. Translate all zx81sd documentation and tooling to English, including file names: - doc/USO.md -> doc/USAGE.md - doc/PRECAUCIONES.md -> doc/PRECAUTIONS.md - doc/CAMBIOS_BASIC.md -> doc/BASIC_CHANGES.md - doc/MAP.md (kept name, content translated) - README.md (content translated) - tools/split_sd81.py, tools/zx81_p_loader.py, tools/boot1.asm (docstrings/comments translated) - backend/main.py, backend/generic.py (comments translated) Co-Authored-By: Claude Sonnet 5 --- src/arch/zx81sd/README.md | 204 +++-- src/arch/zx81sd/backend/generic.py | 10 +- src/arch/zx81sd/backend/main.py | 130 +-- src/arch/zx81sd/doc/BASIC_CHANGES.md | 368 +++++++++ src/arch/zx81sd/doc/CAMBIOS_BASIC.md | 368 --------- src/arch/zx81sd/doc/MAP.md | 1030 ++++++++++++------------ src/arch/zx81sd/doc/PRECAUCIONES.md | 220 ----- src/arch/zx81sd/doc/PRECAUTIONS.md | 216 +++++ src/arch/zx81sd/doc/USAGE.md | 88 ++ src/arch/zx81sd/doc/USO.md | 87 -- src/arch/zx81sd/tools/boot1.asm | 88 +- src/arch/zx81sd/tools/split_sd81.py | 133 +-- src/arch/zx81sd/tools/zx81_p_loader.py | 167 ++-- 13 files changed, 1559 insertions(+), 1550 deletions(-) create mode 100644 src/arch/zx81sd/doc/BASIC_CHANGES.md delete mode 100644 src/arch/zx81sd/doc/CAMBIOS_BASIC.md delete mode 100644 src/arch/zx81sd/doc/PRECAUCIONES.md create mode 100644 src/arch/zx81sd/doc/PRECAUTIONS.md create mode 100644 src/arch/zx81sd/doc/USAGE.md delete mode 100644 src/arch/zx81sd/doc/USO.md diff --git a/src/arch/zx81sd/README.md b/src/arch/zx81sd/README.md index 890495505..3ed854eec 100644 --- a/src/arch/zx81sd/README.md +++ b/src/arch/zx81sd/README.md @@ -1,113 +1,109 @@ -# Arquitectura `zx81sd`: ZX81 + SD81 Booster +# `zx81sd` architecture: ZX81 + SD81 Booster -`zx81sd` es una arquitectura de este compilador para un **ZX81 real con -la tarjeta [SD81 Booster](https://www.sd81.eu/)** (una interfaz que -añade pantalla tipo Spectrum, sonido AY/beeper, mapeador de memoria por -páginas y acceso a tarjeta SD). +`zx81sd` is an architecture for this compiler targeting a **real ZX81 +with the [SD81 Booster](https://www.sd81.eu/) card** (an interface that +adds a Spectrum-like screen, AY/beeper sound, a paged memory mapper and +SD card access). -Todo el código específico de esta arquitectura vive exclusivamente bajo: +All code specific to this architecture lives exclusively under: ``` -src/arch/zx81sd/ (backend del compilador, esta documentación, - y las herramientas de empaquetado en tools/) -src/lib/arch/zx81sd/ (runtime ASM + stdlib BASIC) +src/arch/zx81sd/ (compiler backend, this documentation, + and the packaging tools under tools/) +src/lib/arch/zx81sd/ (ASM runtime + BASIC stdlib) ``` -## Regla de oro del port - -Este compilador es compartido por todas las arquitecturas (zx48k, -zx128k, zxnext...). **El port de zx81sd nunca modifica el frontend ni -la stdlib/runtime compartidos.** El mecanismo de resolución de -`#include`/`#require` busca primero en `src/lib/arch/zx81sd/`, y si no -encuentra el fichero cae automáticamente en `src/lib/arch/zx48k/` (el -fichero compartido). Por eso muchos overrides de zx81sd son copias -completas de la versión zx48k con solo unas pocas líneas cambiadas: hay -que copiar el fichero entero, no un parche — un override parcial -simplemente no existe como concepto aquí. - -Antes de tocar cualquier cosa fuera de `zx81sd/`, para: seguramente hay -una forma de resolverlo con un override. - -## Estado del port - -Funcionalmente completo desde 2026-07-02: FP (RST $28 propio), -gráficos (PLOT/DRAW/arcos/CIRCLE), sonido (BEEP y PLAY sobre AY ZonX y -beeper), teclado (INKEY$/INPUT sobre el teclado físico del ZX81), -joystick, la librería MCU completa (ficheros, RTC/BAT, voz, mapeador de -memoria...) y LOAD/SAVE/VERIFY...CODE contra SD. - -Pendiente / sin auditar, resto de utilidades de pantalla de la stdlib -compartida: - -- `winscroll.bas`: se cree ya portado y probado, pero sin confirmar con - una auditoría formal (no hay override en `zx81sd/stdlib/` — de ser - cierto, es porque no necesitaba ninguno, como `scroll.bas` antes del - fix o `4inarow.bas`). -- `putchars.bas`/`puttile.bas`: sin auditar ni probar. Un vistazo rápido - al fuente no encuentra direcciones de ROM/sysvars del Spectrum - (`putChars` rellena un rectángulo de caracteres, `putTile` coloca un - tile de 16×16 px), así que son buenos candidatos a funcionar sin - cambios, pero no está confirmado. -- `screen.bas`: **sí depende de la ROM** (`$2538`/`$5C65`/`$19E8`, - rutinas y sysvars fijas del Spectrum para leer de vuelta un carácter - de pantalla) — necesitará un override real, no solo una auditoría. -- `print42.bas`/`print64.bas`: **portados** (override completo en - `stdlib/`) — sysvars fijas → equivalentes zx81sd, y las constantes de - base de pantalla/atributos se parchean en tiempo de ejecución - (automodificación de código) en vez de ser fijas. Verificado por - simulación (texto legible píxel a píxel, sin corrupción de memoria); - pendiente de confirmar en el emulador/hardware real. - -Ver [doc/CAMBIOS_BASIC.md](doc/CAMBIOS_BASIC.md) para el patrón general -de este tipo de fix. El port de `maskedsprites.bas`/MSFS (sprites -enmascarados sobre el mapeador de memoria) también está en proceso, aún -sin terminar de funcionar bien. - -## Documentación - -- **[doc/USO.md](doc/USO.md)** — cómo compilar un programa, empaquetarlo - para el ZX81 y cargarlo desde la tarjeta SD. -- **[doc/PRECAUCIONES.md](doc/PRECAUCIONES.md)** — qué hay que tener en - cuenta al escribir o portar software para esta arquitectura (mapa de - memoria, sysvars, teclado, cosas que NO existen aquí aunque existan - en Spectrum). -- **[doc/CAMBIOS_BASIC.md](doc/CAMBIOS_BASIC.md)** — qué cambios de - fuente BASIC hizo falta para portar cada ejemplo oficial de - `examples/` (con el porqué de cada uno). Punto de partida obligado - antes de portar un ejemplo nuevo: casi siempre es uno de los patrones - ya catalogados ahí. -- **[doc/MAP.md](doc/MAP.md)** — bitácora técnica detallada de todos los - bugs encontrados y corregidos durante el port (runtime ASM, no - fuentes BASIC), con la traza de investigación de cada uno. Es el - documento a consultar cuando algo falla en tiempo de ejecución de - forma que recuerda a un bug ya resuelto. - -## Ejemplos - -Los programas de ejemplo ya adaptados/probados para esta arquitectura -están en [`examples/sd81/`](../../../examples/sd81/) (junto al resto de -`examples/` del compilador). El detalle de qué hubo que tocar en cada -uno, y por qué, está en [doc/CAMBIOS_BASIC.md](doc/CAMBIOS_BASIC.md). - -## Herramientas de empaquetado - -[`tools/split_sd81.py`](tools/split_sd81.py) parte el binario plano -generado por el compilador en páginas de 8KB y genera el cargador `.p` -para el ZX81; [`tools/zx81_p_loader.py`](tools/zx81_p_loader.py) es el -tokenizador BASIC que usa para generarlo; [`tools/boot1.asm`](tools/boot1.asm)/ -[`tools/boot1.bin`](tools/boot1.bin) son el cargador de segunda etapa -(stage 1), fijo para todos los programas. Ver -[doc/USO.md](doc/USO.md) para el flujo completo de compilar → -empaquetar → cargar. El conjunto más amplio de fuentes de depuración/ -diagnóstico usados durante el desarrollo del port (no herramientas, -solo pruebas puntuales) vive en un repositorio complementario privado, -no en este. - -## Repositorios relacionados +## The port's golden rule + +This compiler is shared by every architecture (zx48k, zx128k, +zxnext...). **The zx81sd port never modifies the shared frontend or +stdlib/runtime.** The `#include`/`#require` resolution mechanism looks +first in `src/lib/arch/zx81sd/`, and if it doesn't find the file it +automatically falls back to `src/lib/arch/zx48k/` (the shared one). +That's why many zx81sd overrides are full copies of the zx48k version +with just a few lines changed: the whole file has to be copied, not +patched — a partial override simply doesn't exist as a concept here. + +Before touching anything outside `zx81sd/`, stop: there's probably a +way to solve it with an override. + +## Port status + +Functionally complete since 2026-07-02: FP (our own RST $28), graphics +(PLOT/DRAW/arcs/CIRCLE), sound (BEEP and PLAY over the AY ZonX and the +beeper), keyboard (INKEY$/INPUT over the ZX81's physical keyboard), +joystick, the complete MCU library (files, RTC/BAT, voice, memory +mapper...) and LOAD/SAVE/VERIFY...CODE against SD. + +Pending / not audited, remaining screen utilities in the shared stdlib: + +- `winscroll.bas`: believed to be already ported and tested, but not + confirmed by a formal audit (no override in `zx81sd/stdlib/` — if + true, it's because it didn't need one, like `scroll.bas` before its + fix, or `4inarow.bas`). +- `putchars.bas`/`puttile.bas`: not audited or tested. A quick look at + the source finds no Spectrum ROM/sysvar addresses (`putChars` fills a + rectangle of characters, `putTile` places a 16×16 px tile), so + they're good candidates to work unchanged, but this isn't confirmed. +- `screen.bas`: **does depend on the ROM** (`$2538`/`$5C65`/`$19E8`, + fixed Spectrum routines and sysvars to read back a screen character) + — will need a real override, not just an audit. +- `print42.bas`/`print64.bas`: **ported** (full override in `stdlib/`) + — fixed sysvars → zx81sd equivalents, and the screen/attribute base + constants are patched at runtime (self-modifying code) instead of + being fixed. Verified by simulation (legible pixel-by-pixel text, no + memory corruption); pending confirmation on the emulator/real + hardware. + +See [doc/BASIC_CHANGES.md](doc/BASIC_CHANGES.md) for the general +pattern behind this kind of fix. The `maskedsprites.bas`/MSFS port +(masked sprites over the memory mapper) is also in progress, still not +fully working. + +## Documentation + +- **[doc/USAGE.md](doc/USAGE.md)** — how to compile a program, package + it for the ZX81 and load it from the SD card. +- **[doc/PRECAUTIONS.md](doc/PRECAUTIONS.md)** — what to keep in mind + when writing or porting software for this architecture (memory map, + sysvars, keyboard, things that do NOT exist here even though they do + on a Spectrum). +- **[doc/BASIC_CHANGES.md](doc/BASIC_CHANGES.md)** — what BASIC source + changes were needed to port each official example in `examples/` + (with the why for each one). Mandatory starting point before porting + a new example: it's almost always one of the patterns already + cataloged there. +- **[doc/MAP.md](doc/MAP.md)** — detailed technical log of every bug + found and fixed during the port (ASM runtime, not BASIC sources), + with the investigation trace for each one. The document to check + when something fails at runtime in a way that resembles an + already-solved bug. + +## Examples + +The example programs already adapted/tested for this architecture are +in [`examples/sd81/`](../../../examples/sd81/) (alongside the rest of +the compiler's `examples/`). The detail of what had to be changed in +each one, and why, is in +[doc/BASIC_CHANGES.md](doc/BASIC_CHANGES.md). + +## Packaging tools + +[`tools/split_sd81.py`](tools/split_sd81.py) splits the flat binary +produced by the compiler into 8KB pages and generates the `.p` loader +for the ZX81; [`tools/zx81_p_loader.py`](tools/zx81_p_loader.py) is the +BASIC tokenizer it uses to generate it; [`tools/boot1.asm`](tools/boot1.asm)/ +[`tools/boot1.bin`](tools/boot1.bin) are the second-stage loader (stage +1), fixed for every program. See [doc/USAGE.md](doc/USAGE.md) for the +full compile → package → load flow. The wider set of debugging/ +diagnostic sources used during the port's development (not tools, just +one-off tests) lives in a private companion repository, not this one. + +## Related repositories - **[SD81 Booster](https://codeberg.org/Retrostuff/SD81-Booster)** — - firmware/hardware de la interfaz para la que se ha hecho este port. + the hardware/firmware for the interface this port targets. - **[EightyOne Cross-platform](https://codeberg.org/wilco2009/EightyOne-CrossPlatform)** — - emulador usado durante el desarrollo para probar sin hardware real. -- **[CPM_SD81](https://codeberg.org/wilco2009/CPM_SD81)** — CP/M sobre - el SD81 Booster. + the emulator used during development to test without real hardware. +- **[CPM_SD81](https://codeberg.org/wilco2009/CPM_SD81)** — CP/M on the + SD81 Booster. diff --git a/src/arch/zx81sd/backend/generic.py b/src/arch/zx81sd/backend/generic.py index 196556ddc..f9492c660 100644 --- a/src/arch/zx81sd/backend/generic.py +++ b/src/arch/zx81sd/backend/generic.py @@ -1,6 +1,6 @@ # -------------------------------------------------------------------- # SPDX-License-Identifier: AGPL-3.0-or-later -# ZX81 + SD81 Booster — handler del opcode END +# ZX81 + SD81 Booster — END opcode handler # -------------------------------------------------------------------- from src.arch.interface.quad import Quad @@ -8,11 +8,11 @@ def _end(ins: Quad): - """Secuencia de fin de programa para ZX81 + SD81 Booster. + """End-of-program sequence for ZX81 + SD81 Booster. - No hay ROM ni BASIC al que volver: detiene la CPU de forma segura. - Si END aparece varias veces en el programa (salidas anticipadas), - los casos posteriores generan un JP al primer bloque END emitido. + There's no ROM or BASIC to return to: it stops the CPU safely. + If END appears more than once in the program (early exits), later + occurrences generate a JP to the first emitted END block. """ output = Bits16.get_oper(ins[1]) output.append("ld b, h") diff --git a/src/arch/zx81sd/backend/main.py b/src/arch/zx81sd/backend/main.py index c4e1378af..a3d3ecc9a 100644 --- a/src/arch/zx81sd/backend/main.py +++ b/src/arch/zx81sd/backend/main.py @@ -14,47 +14,47 @@ from .generic import _end # --------------------------------------------------------------------------- -# Constantes de arquitectura ZX81 + SD81 Booster +# ZX81 + SD81 Booster architecture constants # --------------------------------------------------------------------------- -# Mapa de memoria (flat, ORG $0000): -# $0000-$00FF vectors.asm — vectores RST + relleno hasta $0100 -# $0100-$0FFF stage 2 bootstrap (prólogo) + rutinas de sistema -# $1000-$7FFF runtime ZX BASIC + código del usuario (28 KB) -# $8000-$80FF sysvars del runtime -# $8100-$BFFF heap + datos del usuario (~15.75 KB) -# $C000-$D7FF bitmap pantalla Spectrum (bloque 6, página dedicada) -# $D800-$DAFF atributos pantalla -# $E000-$FFFF bloque 7 — banking de datos (mapas, sprites...) +# Memory map (flat, ORG $0000): +# $0000-$00FF vectors.asm — RST vectors + padding up to $0100 +# $0100-$0FFF stage 2 bootstrap (prologue) + system routines +# $1000-$7FFF ZX BASIC runtime + user code (28 KB) +# $8000-$80FF runtime sysvars +# $8100-$BFFF heap + user data (~15.75 KB) +# $C000-$D7FF Spectrum screen bitmap (block 6, dedicated page) +# $D800-$DAFF screen attributes +# $E000-$FFFF block 7 — data banking (maps, sprites...) -_ORG = 0x0000 # el binario comienza en $0000 (vectors.asm lo rellena hasta $0100) -_STAGE2_ENTRY = 0x0100 # punto de entrada del stage 2 bootstrap +_ORG = 0x0000 # the binary starts at $0000 (vectors.asm pads up to $0100) +_STAGE2_ENTRY = 0x0100 # stage 2 bootstrap's entry point -_HEAP_ADDR = 0x8100 # heap en zona de datos ($8100-$BFFF) +_HEAP_ADDR = 0x8100 # heap in the data area ($8100-$BFFF) _HEAP_SIZE = 0x3EFF # ~15.75 KB -# Páginas SD81 asignadas a cada bloque (las carga el BASIC antes del salto) -# Página 8 → bloque 0 ($0000-$1FFF) ← el stage 1 solo mapea este -# Página 9 → bloque 1 ($2000-$3FFF) ┐ -# Página 10 → bloque 2 ($4000-$5FFF) │ el stage 2 (aquí) mapea estos -# Página 11 → bloque 3 ($6000-$7FFF) │ -# Página 12 → bloque 4 ($8000-$9FFF) │ (datos, no ejecutable sin MC45) -# Página 13 → bloque 5 ($A000-$BFFF) ┘ +# SD81 pages assigned to each block (loaded by BASIC before the jump) +# Page 8 -> block 0 ($0000-$1FFF) <- stage 1 only maps this one +# Page 9 -> block 1 ($2000-$3FFF) ┐ +# Page 10 -> block 2 ($4000-$5FFF) │ stage 2 (here) maps these +# Page 11 -> block 3 ($6000-$7FFF) │ +# Page 12 -> block 4 ($8000-$9FFF) │ (data, not executable without MC45) +# Page 13 -> block 5 ($A000-$BFFF) ┘ _PAGE_MAP = [ - (1, 9), # bloque 1 → página 9 - (2, 10), # bloque 2 → página 10 - (3, 11), # bloque 3 → página 11 - (4, 12), # bloque 4 → página 12 - (5, 13), # bloque 5 → página 13 + (1, 9), # block 1 -> page 9 + (2, 10), # block 2 -> page 10 + (3, 11), # block 3 -> page 11 + (4, 12), # block 4 -> page 12 + (5, 13), # block 5 -> page 13 ] -_SD81_PAGE_PORT = 0xE7 # puerto mapeador de memoria (modo full: OUT (C), A) -_STACK_TOP = 0x7FFF # pila al tope de la zona ejecutable +_SD81_PAGE_PORT = 0xE7 # memory mapper port (full mode: OUT (C), A) +_STACK_TOP = 0x7FFF # stack at the top of the executable area def _map_block(block: int, page: int) -> list[str]: - """Emite OUT (C), A para mapear una página a un bloque (modo full, 64 pág.).""" + """Emits OUT (C), A to map a page to a block (full mode, 64 pages).""" return [ f"ld b, {page}", f"ld a, {block}", @@ -69,13 +69,14 @@ def init(self): OPTIONS(Action.ADD_IF_NOT_DEFINED, name="org", type=int, default=_ORG) - # El backend Z80 generico (super().init(), justo arriba) ya registra - # "heap_size" (4768) y "heap_address" (None) con ADD_IF_NOT_DEFINED, - # asi que un ADD_IF_NOT_DEFINED aqui seria un no-op y el heap acababa - # reservado inline (DEFS) con 4768 bytes dentro de la zona ejecutable. - # Se asigna directamente para que el heap viva en la zona de datos - # $8100-$BFFF; los flags --heap-address/-H de la CLI se aplican - # despues y siguen pudiendo sobreescribir estos valores. + # The generic Z80 backend (super().init(), right above) already + # registers "heap_size" (4768) and "heap_address" (None) with + # ADD_IF_NOT_DEFINED, so an ADD_IF_NOT_DEFINED here would be a + # no-op and the heap would end up reserved inline (DEFS) with + # 4768 bytes inside the executable area. Assigned directly so + # the heap lives in the data area $8100-$BFFF; the CLI's + # --heap-address/-H flags are applied afterward and can still + # override these values. OPTIONS.heap_size = _HEAP_SIZE OPTIONS.heap_address = _HEAP_ADDR @@ -97,23 +98,24 @@ def init(self): @staticmethod def emit_prologue() -> list[str]: """ - Prólogo del programa para ZX81 + SD81 Booster. + Program prologue for ZX81 + SD81 Booster. - Estructura del binario generado (ORG $0000): - $0000-$00FF vectors.asm (vectores RST, incluido desde sysvars.asm) - $0100 START_LABEL (entrada del stage 2 bootstrap) - - mapeo de bloques 1-5 a sus páginas definitivas + Structure of the generated binary (ORG $0000): + $0000-$00FF vectors.asm (RST vectors, included from sysvars.asm) + $0100 START_LABEL (stage 2 bootstrap's entry point) + - mapping blocks 1-5 to their final pages - SP = $7FFF - - CALL (SD81_INIT_SYSVARS, etc.) + - CALL <#init routines> (SD81_INIT_SYSVARS, etc.) - JP __MAIN_LABEL__ - El stage 1 bootstrap (externo, en el cargador BASIC a $6000) ya ha: - - Configurado HFILE=$C000 y activado modo Spectrum (POKE 2045, 172) - - Desactivado el IO mapeado en memoria (POKE 2056) - - Desactivado interrupciones (DI) - - Mapeado bloque 0 → página 8 (JP $0100 ya ejecuta en RAM limpia) + The (external) stage 1 bootstrap, in the BASIC loader at $6000, + has already: + - Set HFILE=$C000 and activated Spectrum mode (POKE 2045, 172) + - Disabled memory-mapped IO (POKE 2056) + - Disabled interrupts (DI) + - Mapped block 0 -> page 8 (JP $0100 now runs on clean RAM) """ - # -- Definiciones del heap ------------------------------------------ + # -- Heap definitions ------------------------------------------ heap_init = [f"{common.DATA_LABEL}:"] if common.REQUIRES.intersection(common.MEMINITS) or f"{NAMESPACE}.__MEM_INIT" in common.INITS: @@ -143,17 +145,17 @@ def emit_prologue() -> list[str]: f"{NAMESPACE}.__LABEL__.ZXBASIC_USER_DATA EQU {common.DATA_LABEL}" ) - # -- Tabla de vectores RST ($0000-$00FF) ---------------------------- - # Debe ser lo primero en el binario. Cada RST ocupa 8 bytes. - # Usamos org absoluto para cada entrada: el ensamblador rellena los - # huecos con ceros, lo que es correcto para una zona no ejecutable. + # -- RST vector table ($0000-$00FF) --------------------------------- + # Must be the very first thing in the binary. Each RST takes 8 bytes. + # An absolute org is used for every entry: the assembler fills the + # gaps with zeros, which is correct for a non-executable area. output = ["org $0000"] - output.append("jp $0100") # $0000: reset / RST 0 → stage 2 + output.append("jp $0100") # $0000: reset / RST 0 -> stage 2 output.append("org $0008") - output.append("di") # $0008: RST $08 (error handler Spectrum) + output.append("di") # $0008: RST $08 (Spectrum error handler) output.append("halt") output.append("org $0010") - output.append("di") # $0010-$0037: RSTs no usados + output.append("di") # $0010-$0037: unused RSTs output.append("halt") output.append("org $0018") output.append("di") @@ -162,17 +164,17 @@ def emit_prologue() -> list[str]: output.append("di") output.append("halt") output.append("org $0028") - output.append("jp .core.FP_CALC_ENTRY") # $0028: RST $28 -> calculador FP propio + output.append("jp .core.FP_CALC_ENTRY") # $0028: RST $28 -> our own FP calculator output.append("org $0030") output.append("di") output.append("halt") output.append("org $0038") - output.append("di") # $0038: RST $38 IM1 — DI permanente, nunca llega + output.append("di") # $0038: RST $38 IM1 — permanent DI, never reached output.append("halt") output.append("org $0066") - output.append("retn") # $0066: NMI desactivada, pero el vector debe existir + output.append("retn") # $0066: NMI disabled, but the vector must exist - # -- Stage 2 bootstrap en $0100 ------------------------------------ + # -- Stage 2 bootstrap at $0100 ------------------------------------ output.append(f"org {_STAGE2_ENTRY}") output.append(f"{common.START_LABEL}:") @@ -180,19 +182,19 @@ def emit_prologue() -> list[str]: output.extend(heap_init) return output - # Mapeo de bloques 1-5 a sus páginas definitivas. - # El bloque 0 ya fue mapeado a la página 8 por el stage 1 externo. + # Mapping blocks 1-5 to their final pages. + # Block 0 was already mapped to page 8 by the external stage 1. for block, page in _PAGE_MAP: output.extend(_map_block(block, page)) - # Pila al tope de la zona ejecutable + # Stack at the top of the executable area output.append(f"ld sp, {_STACK_TOP:#06x}") - # Llamadas a rutinas de inicialización registradas con #init - # (SD81_INIT_SYSVARS y cualquier otra del runtime incluido) + # Calls to initialization routines registered with #init + # (SD81_INIT_SYSVARS and any other one from the included runtime) output.extend(f"call {label}" for label in sorted(common.INITS)) - # Salto al programa del usuario + # Jump to the user's program output.append(f"jp {common.MAIN_LABEL}") output.extend(heap_init) diff --git a/src/arch/zx81sd/doc/BASIC_CHANGES.md b/src/arch/zx81sd/doc/BASIC_CHANGES.md new file mode 100644 index 000000000..13dde89f5 --- /dev/null +++ b/src/arch/zx81sd/doc/BASIC_CHANGES.md @@ -0,0 +1,368 @@ +# BASIC source changes needed to port official examples to zx81sd + +This file records, for every official `examples/` sample tried on +zx81sd, whether the BASIC source needed touching (never the original: +always an adapted copy in `examples/sd81/`) or just different compile +flags. + +Ground rule: the official zxbasic source **is never modified**. When a +program depends on a Spectrum-specific sysvar or absolute address, an +adapted copy is made next to the original. + +--- + +## 1. `examples/english/comecoquitos.bas` — NO source changes + +Compiles as-is. Only needs command-line flags: + +``` +python -m src.zxbc.zxbc comecoquitos.bas --arch zx81sd --string-base 1 --array-base 1 -o comecocos.bin +``` + +**Why**: the source uses 1-based string indexing (classic Sinclair +BASIC style, `l$(f)`, slicing). With zxbasic's default 0-based indexing, +the collision comparisons end up shifted by one position — not a bug in +the port, it happens exactly the same on zx48k without those flags. + +--- + +## 2. `examples/english/snake_en.bas` → `examples/sd81/snake_sd81.bas` + +One single-line change: + +```diff +-73 POKE UINTEGER 23675, @udg(0, 0): REM Sets UDG variable to first element ++73 POKE UINTEGER $8002, @udg(0, 0): REM zx81sd's UDG sysvar (was 23675 on Spectrum) +``` + +**Why**: `23675` ($5C7B) is the absolute address of the `UDG` sysvar in +the **Spectrum**'s memory map. On zx81sd the same sysvar lives at +`$8002` (see `SYSVAR_BASE+2` in `src/lib/arch/zx81sd/runtime/sysvars.asm`). +Without the change, the POKE lands in free RAM and the UDGs (the +snake's head and fruit) never activate — the game would run but draw +blank spaces instead of the graphics. + +**General pattern**: any example that does `POKE`/`PEEK` on a Spectrum +sysvar address instead of using the higher-level function (here it +would have been enough not to use raw UDG) needs this kind of address +translation. See also flights.bas below. + +--- + +## 3. `examples/flights.bas` → `examples/sd81/flights_sd81.bas` + +Three content changes (plus cosmetic trailing-whitespace cleanup with +no functional effect, not listed): + +### 3.1 Remove a Spectrum sysvar POKE with no equivalent + +```diff +-1 POKE 23658,8: BORDER 1: PAPER 1: INK 7: CLS ++1 BORDER 1: PAPER 1: INK 7: CLS +``` + +**Why**: `23658` ($5C6A) is `REPDEL` (key repeat delay) on the +Spectrum — that sysvar doesn't exist on zx81sd, and the POKE would +write to an arbitrary RAM address. Simply removed: the effect +(adjusting keyboard auto-repeat) has no equivalent and isn't needed by +the game. + +### 3.2 Translate the COORDS sysvar to its real zx81sd address + +```diff +-2295 IF gc<>0 THEN PLOT OVER 1;x1,168+16-y1: DRAW OVER 1;x2-PEEK 23677,168+16-y2-PEEK 23678: END IF ++2295 IF gc<>0 THEN PLOT OVER 1;x1,168+16-y1: DRAW OVER 1;x2-PEEK $8004,168+16-y2-PEEK $8005: END IF +``` +(and three more identical occurrences on lines 2370, 2378 and 2445) + +**Why**: `23677`/`23678` ($5C7D/$5C7E) are the Spectrum's `COORDS` +sysvar (last `PLOT` coordinate). On zx81sd it lives at `$8004`/`$8005` +(`COORDS EQU SYSVAR_BASE+$04` in `sysvars.asm`). Without the +translation, the `PEEK`s read the compiled program's own RAM instead +of the actual coordinate, and the horizon-line `DRAW` comes out with a +random offset depending on that RAM's contents — this was the first +symptom reported ("the horizon line draws wrong"). + +### 3.3 Key comparisons to lowercase + +```diff +-3010 IF k$="S" THEN LET pow=pow-1: END IF +-3020 IF k$="F" THEN LET pow=pow+1: END IF +-3030 IF k$="Q" THEN LET pt=pt+1: END IF +-3040 IF k$="A" THEN LET pt=pt-1: END IF +-3050 IF k$="O" AND rl>-30 THEN LET rl=rl-1: END IF +-3060 IF k$="P" AND rl<30 THEN LET rl=rl+1: END IF ++3010 IF k$="s" THEN LET pow=pow-1: END IF ++3020 IF k$="f" THEN LET pow=pow+1: END IF ++3030 IF k$="q" THEN LET pt=pt+1: END IF ++3040 IF k$="a" THEN LET pt=pt-1: END IF ++3050 IF k$="o" AND rl>-30 THEN LET rl=rl-1: END IF ++3060 IF k$="p" AND rl<30 THEN LET rl=rl+1: END IF +``` +(and the "Y"/"N" comparisons on lines 6150/6160) + +**Why (this change matches the old keyboard scheme — see the "no +longer needed" note below)**: at the time `flights.bas` was ported, +`zx81sd/runtime/io/keyboard/keyscan.asm` (a hand-written rescan of the +**ZX81's own physical keyboard**, since the SD81 Booster has no +Spectrum keyboard) only ever returned lowercase, regardless of +whether `SHIFT` was pressed — `SHIFT+letter` didn't produce the +uppercase of that letter, but one of the ZX81's classic symbols (colon, +quotes, `+`, `-`...). With that scheme there was no physical +combination that reproduced Spectrum-style "CAPS SHIFT+S", and the only +possible adaptation was comparing against the unshifted lowercase. +Same reason, already solved a different way in `comecoquitos.bas` +(there the source already compared in lowercase, "o","p","q","a","y","n" +— its author just happened to type it that way, nothing we had to +touch) and in `snake_sd81.bas` (compares "O"/"o" with `OR`, covering +both cases without needing any change). + +**No longer needed (2026-07-04)**: `keyscan.asm` was redesigned so a +direct key press gives lowercase and `SHIFT+letter` gives the +UPPERCASE of that letter (plus `SHIFT+"2"` as a persistent CAPS LOCK, +and the ZX81's classic symbols reachable with `"."+key` from +`INPUT()`). With the current scheme, `INKEY$="S"` would already work +naturally by pressing `SHIFT+S`, exactly like on a Spectrum — the +case change in `flights_sd81.bas` documented above was necessary at the +time but wouldn't be needed if ported today from scratch. Kept here as +a record instead of reverting the already-tested copy. Full detail of +the redesign in [MAP.md](MAP.md), section "New keyboard scheme". + +--- + +## 4. `examples/english/4inarow.bas` — NO source changes + +Compiles with default flags: + +``` +python -m src.zxbc.zxbc 4inarow.bas --arch zx81sd -o row4.bin +``` + +Its arrays use indices 1..8, which fit in the default 0-based indexing +without any slicing collision (it doesn't do string slicing), and its +key comparisons already use lowercase ("y"/"n" in the prompts). It +exercises the arc code (`DRAW 8,0,PI`) and `CIRCLE` without needing any +patch. + +--- + +## 5. `examples/scroll.bas` — NO source changes + +Compiles with default flags: + +``` +python -m src.zxbc.zxbc scroll.bas --arch zx81sd -o scroll.bin +``` + +The problem wasn't in the example but in the library: `scroll.bas` had +no zx81sd version — the zx48k one was used as-is, and its 8 subs +(`ScrollRight/Left/Up/Down` + their `*Aligned` variants) call `$22AC` +(the Spectrum ROM's *PIXEL-ADD* routine, which doesn't exist on +zx81sd). `src/lib/arch/zx81sd/stdlib/scroll.bas` was created, identical +except those 8 calls are replaced with `call PIXEL_ADDR` (our own +implementation, same register contract as the ROM — see +[MAP.md](MAP.md)). The example itself needed no change, same as +4inarow.bas. + +--- + +## 6. `examples/maskedsprites.bas` → `examples/sd81/maskedsprites_sd81.bas` + +**In progress — still not fully working.** + +One substantial change: `WaitForNewFrame` (defined in the example +itself, not the library) rewritten to not depend on interrupts: + +```diff +- ld de,23672 +- ld c,a ; A = C = minimumNumberofFramesToWaitSinceLastWait +- READ_IFF2 +- ex af,af' +- ei ; interrupts MUST be enabled before HALT +- halt ++ ld de, FRAMES ++ ld c,a ; C = minimumNumberofFramesToWaitSinceLastWait ++ call VSYNC_TICK ; guarantees at least one frame waited (replaces EI+HALT) + wait: + ld a,(de) + sub (hl) + cp c +- jr c,wait +- ld a,(de) +- ld (hl),a +- ex af,af' +- ret pe +- di +- RET ++ jr nc,enough ++ call VSYNC_TICK ++ jr wait ++enough: ++ ld a,(de) ++ ld (hl),a ++ ret +``` + +**Why**: `23672` is the Spectrum ROM's `FRAMES` counter, automatically +incremented by the 50Hz IM1 interrupt. The original does one initial +`HALT` (waits for the interrupt) and then a loop that trusts the +interrupt to keep incrementing it in the background while the loop just +*checks* without waiting again. On zx81sd there are no interrupts +(permanent DI; the `$0038` vector is a `DI;HALT` trap, not a real +handler) — that `HALT` would never wake up. It's replaced with +`VSYNC_TICK` (polling the SD81 Booster hardware's VSYNC pulse counter +through a port, the same routine `PAUSE` already uses), called +explicitly on every loop iteration that needs to wait. + +**Update — MSFS ported to the zx81sd mapper (block 7)**: the risk +above no longer applies. `src/lib/arch/zx81sd/stdlib/cb/maskedsprites.bas` +was created (a full override of the shared library, which stays +untouched): `SetBankPreservingINTs`/`GetBankPreservingRegs` rewritten +in hand-written ASM over port `$E7` (block 7, `$E000-$FFFF` — reserved +in our memory map for "data banking, maps, sprites"), and +`MaskedSpritesFileSystemStart` fixed at `$E000` instead of "whatever's +left up to `$FFFF`" (which assumed flat Spectrum RAM). The rest of MSFS +(hundreds of lines of block-bitmap arithmetic) is bank/address-agnostic +and was copied without touching a single line. Full detail, including a +real bug caught in the process (registers not preserved when written +in BASIC instead of ASM), in [MAP.md](MAP.md). + +Despite the fixes above (confirmed by simulation and, partially, on a +real emulator/hardware), the library in its current state **still +doesn't fully work** — work in progress, don't consider this example +closed yet. + +--- + +## 7. `examples/sd81/pong.bas` — classic transcription, one ASM change + +Not an official `examples/` sample but a classic Sinclair-BASIC-style +transcription of Pong (GOSUB/line numbers), added directly under +`examples/sd81/`. It already uses lowercase key comparisons +("4","q","3","a"), matching the current keyboard scheme with no change +needed — the only change was replacing the screen sync: + +```diff + ASM +- call VSYNC_TICK ; guarantees at least one frame waited (replaces EI+HALT) ++ call .core.VSYNC_TICK ; guarantees at least one frame waited (replaces EI+HALT) + ; halt ; Avoids screen flickering + END ASM +``` + +**Why**: `vsync.asm` wraps `VSYNC_TICK` in +`push namespace core ... pop namespace`; calling it from an +`ASM ... END ASM` block that isn't inside that namespace needs the +full `.core.VSYNC_TICK` prefix — the same error pattern as in +`maskedsprites_sd81.bas` (see [PRECAUTIONS.md](PRECAUTIONS.md), +section 5). Without the prefix, the compiler gives +`Undefined GLOBAL label '.VSYNC_TICK'`. + +--- + +## 8. `examples/sd81/block7test.bas` — no official equivalent, mapper test + +A minimal program (not a transcription of any example) written to +confirm, in isolation, that the SD81 Booster's memory mapper provides +independent storage per page: it writes different patterns to pages 20 +and 63 of block 7 (`$E000-$FFFF`) via `Map()` and checks they don't +overwrite each other. It was used to rule out the mapper as a cause +during the MSFS bug investigation (see [MAP.md](MAP.md)). Kept as a +reference example of direct `Map()`/`MapGet` usage over block 7. + +--- + +## 9. `print42.bas`/`print64.bas` — new libraries (no example changes) + +`stdlib/print42.bas` and `stdlib/print64.bas` (Britlion) implement a +42/64-column `PRINT` drawing pixel by pixel, instead of the normal +32-column `PRINT`. The zx48k version depends on the Spectrum ROM in +several places: + +- `print42.bas`: `ld hl,$5C7B` (`UDG` sysvar), `ld de,15360` + (fixed `CHARS-256`) and `ld a,(23693)` (`ATTR_P` sysvar). +- `print64.bas`: only `ld a,(23693)` (`ATTR_P`) — its 4px charset is + its own, it doesn't use `UDG`/`CHARS`. +- Both, additionally, place the screen and attributes using + **immediate** fixed-base constants (`add a,64` / `add a,88`, the + high bytes of `$4000`/`$5800`) instead of reading a pointer — this + works on the Spectrum because those addresses are ROM literals, but + on zx81sd `SCREEN_ADDR`/`SCREEN_ATTR_ADDR` are variables (block 6, + `$C000`/`$D800`). + +`src/lib/arch/zx81sd/stdlib/print42.bas` and `print64.bas` were +created (full overrides): fixed sysvars → their zx81sd equivalents +(`UDG`/`CHARS`/`ATTR_P`, see `sysvars.asm`), and the two screen/ +attribute base constants are **patched once on entering the function** +(self-modifying code on the `ADD A,n` instruction itself), reading the +real high byte of `SCREEN_ADDR`/`SCREEN_ATTR_ADDR` at that point — the +same thing the original ROM did with its own routines, only with a +variable value instead of a fixed one. + +**Real bug caught during the port** (not in the example, in the +library itself): a `#include once ` naively placed at the +top of the file (BASIC level, outside any `ASM` block) produced +`error: illegal preprocessor character`, and placed inside the +function's `ASM` block it compiled but hung the program at runtime +(`HALT` at a garbage code address). Cause: if this library happens to +be the **first** one to include `sysvars.asm` in the whole program, it +drags in `bootstrap.asm`/`charset.asm` behind it — and the latter does +`INCBIN "specfont.bin"` (the complete font, not assembly text) — right +at that point in the source file. At BASIC level, the BASIC lexer tries +to tokenize binary source bytes as if they were text (hence the +"illegal preprocessor character"). Inside the function, the font binary +gets emitted literally in the middle of the function's compiled body, +and the CPU "executes" it as if it were instructions the moment control +flow falls there. Fix: don't include `sysvars.asm` at all from these +files — they trust that it's already included by the rest of the +runtime (`CLS`/`PRINT`, which any program using `print42`/`print64` +will also use), and only reference the already-namespaced symbols +(`.core.CHARS`, etc.). Full detail in [PRECAUTIONS.md](PRECAUTIONS.md) +and [MAP.md](MAP.md). + +Verified by simulation (the usual Python harness): both test strings +(`tests_debug/print4264.bas`, companion repository) render legibly +pixel by pixel at the expected position, with no writes outside +screen/attributes/the file's own variables, and the program reaches its +normal `__END_PROGRAM`/`HALT`. Still pending confirmation on the +emulator/real hardware. + +--- + +## Summary for the manual + +| Example | zx81sd copy | Source changes | Compile flags | +|---|---|---|---| +| comecoquitos.bas | no copy needed if the original isn't touched | none | `--string-base 1 --array-base 1` | +| snake_en.bas | `snake_sd81.bas` | 1 line (UDG address) | none special | +| flights.bas | `flights_sd81.bas` | 3 kinds of change (POKE removed, 4× PEEK COORDS, 8× key case) | none special | +| 4inarow.bas | no copy needed | none | none special | +| scroll.bas | no copy needed | none (the fix was in the `stdlib/scroll.bas` library) | none special | +| maskedsprites.bas | `maskedsprites_sd81.bas` | `WaitForNewFrame` rewritten (EI+HALT → VSYNC_TICK); `cb/maskedsprites.bas` library ported to the mapper (block 7) — **in progress, still not fully working** | none special | +| pong.bas (unofficial) | `pong.bas` in `examples/sd81/` | 1 ASM line (VSYNC_TICK namespace) | none special | +| block7test.bas (unofficial) | `block7test.bas` in `examples/sd81/` | n/a (written directly for zx81sd) | none special | +| print42.bas/print64.bas (libraries) | not applicable (not examples) | none — the fix was in `stdlib/print42.bas`/`print64.bas` | none special | + +Pattern identified for future examples: + +1. Look for `POKE`/`PEEK` on numeric literals: it's almost always a + Spectrum sysvar that needs translating to its zx81sd equivalent + address (see `src/lib/arch/zx81sd/runtime/sysvars.asm`). +2. `INKEY$` comparisons against uppercase control keys (`"S"`, `"Q"`, + etc.): with the **current** keyboard scheme (direct press = + lowercase, `SHIFT+letter` = uppercase) this already works exactly + like on a Spectrum and **needs no change at all** — the case change + documented above for `flights.bas` corresponds to an earlier version + of `keyscan.asm` (see the "no longer needed" note in section 3.3) + and is kept here only as a historical record of that + already-generated copy. +3. Inline ASM code calling routines wrapped in `push namespace core` + (like `VSYNC_TICK`) needs the `.core.` prefix if the `ASM` block + calling it isn't already inside that namespace (see the `pong.bas` + case above). +4. If a new library needs its own sysvars (`CHARS`/`UDG`/`ATTR_P`/ + `SCREEN_ADDR`/...), **don't do `#include once ` inside + the file** — only reference the symbols with the `.core.` prefix, + trusting that the rest of the runtime (`CLS`/`PRINT`) already + included it. See the `print42.bas`/`print64.bas` case above and + [PRECAUTIONS.md](PRECAUTIONS.md). diff --git a/src/arch/zx81sd/doc/CAMBIOS_BASIC.md b/src/arch/zx81sd/doc/CAMBIOS_BASIC.md deleted file mode 100644 index 56eaa17fa..000000000 --- a/src/arch/zx81sd/doc/CAMBIOS_BASIC.md +++ /dev/null @@ -1,368 +0,0 @@ -# Cambios en fuentes BASIC para portar ejemplos oficiales a zx81sd - -Este fichero recopila, para cada ejemplo oficial de `examples/` que se ha -probado en zx81sd, si hizo falta tocar el fuente BASIC (nunca el original: -siempre una copia en `examples/sd81/`) o solo cambiar las flags de -compilación. - -Regla de fondo: el fuente oficial de zxbasic **nunca se modifica**. Cuando -un programa depende de una sysvar o dirección absoluta específica del -Spectrum, se hace una copia adaptada junto al original. - ---- - -## 1. `examples/english/comecoquitos.bas` — SIN cambios de fuente - -Compila tal cual. Solo necesita flags de línea de comandos: - -``` -python -m src.zxbc.zxbc comecoquitos.bas --arch zx81sd --string-base 1 --array-base 1 -o comecocos.bin -``` - -**Por qué**: el fuente usa indexación de strings 1-based (estilo Sinclair -BASIC clásico, `l$(f)`, slicing). Con la base 0 por defecto de zxbasic, -las comparaciones de colisión salen desplazadas una posición — no es un -bug del port, en zx48k pasa exactamente igual sin esas flags. - ---- - -## 2. `examples/english/snake_en.bas` → `examples/sd81/snake_sd81.bas` - -Un solo cambio, una línea: - -```diff --73 POKE UINTEGER 23675, @udg(0, 0): REM Sets UDG variable to first element -+73 POKE UINTEGER $8002, @udg(0, 0): REM sysvar UDG de zx81sd (en Spectrum era 23675) -``` - -**Por qué**: `23675` ($5C7B) es la dirección absoluta del sysvar `UDG` en -el mapa de memoria del **Spectrum**. En zx81sd el mismo sysvar vive en -`$8002` (ver `SYSVAR_BASE+2` en `src/lib/arch/zx81sd/runtime/sysvars.asm`). -Sin el cambio, el POKE cae en RAM libre y los UDGs (cabeza y fruta de la -serpiente) nunca se activarían — el juego funcionaría pero pintaría -espacios en vez de los gráficos. - -**Patrón general**: cualquier ejemplo que haga `POKE`/`PEEK` a una -dirección de sysvar del Spectrum en vez de usar la función de más alto -nivel (aquí habría bastado con no usar UDG a pelo) necesita este tipo de -traducción de dirección. Ver también flights.bas más abajo. - ---- - -## 3. `examples/flights.bas` → `examples/sd81/flights_sd81.bas` - -Tres cambios de contenido (más limpieza cosmética de espacios finales sin -efecto funcional, no listada): - -### 3.1 Eliminar un POKE de sysvar del Spectrum sin equivalente - -```diff --1 POKE 23658,8: BORDER 1: PAPER 1: INK 7: CLS -+1 BORDER 1: PAPER 1: INK 7: CLS -``` - -**Por qué**: `23658` ($5C6A) es `REPDEL` (retardo de repetición de tecla) -en el Spectrum — no existe ese sysvar en zx81sd, y el POKE escribiría -sobre una dirección de RAM arbitraria. Se elimina sin más: el efecto -(ajustar el auto-repeat del teclado) no tiene equivalente ni falta le -hace al juego. - -### 3.2 Traducir el sysvar COORDS a su dirección real en zx81sd - -```diff --2295 IF gc<>0 THEN PLOT OVER 1;x1,168+16-y1: DRAW OVER 1;x2-PEEK 23677,168+16-y2-PEEK 23678: END IF -+2295 IF gc<>0 THEN PLOT OVER 1;x1,168+16-y1: DRAW OVER 1;x2-PEEK $8004,168+16-y2-PEEK $8005: END IF -``` -(y tres apariciones más idénticas en las líneas 2370, 2378 y 2445) - -**Por qué**: `23677`/`23678` ($5C7D/$5C7E) son el sysvar `COORDS` -(última coordenada de `PLOT`) del Spectrum. En zx81sd vive en `$8004`/ -`$8005` (`COORDS EQU SYSVAR_BASE+$04` en `sysvars.asm`). Sin la -traducción, los `PEEK` leen RAM del programa compilado en vez de la -coordenada real, y el `DRAW` de la línea del horizonte sale con un -desplazamiento aleatorio dependiente del contenido de esa RAM — este fue -el primer síntoma reportado ("pinta mal la línea del horizonte"). - -### 3.3 Comparaciones de tecla a minúsculas - -```diff --3010 IF k$="S" THEN LET pow=pow-1: END IF --3020 IF k$="F" THEN LET pow=pow+1: END IF --3030 IF k$="Q" THEN LET pt=pt+1: END IF --3040 IF k$="A" THEN LET pt=pt-1: END IF --3050 IF k$="O" AND rl>-30 THEN LET rl=rl-1: END IF --3060 IF k$="P" AND rl<30 THEN LET rl=rl+1: END IF -+3010 IF k$="s" THEN LET pow=pow-1: END IF -+3020 IF k$="f" THEN LET pow=pow+1: END IF -+3030 IF k$="q" THEN LET pt=pt+1: END IF -+3040 IF k$="a" THEN LET pt=pt-1: END IF -+3050 IF k$="o" AND rl>-30 THEN LET rl=rl-1: END IF -+3060 IF k$="p" AND rl<30 THEN LET rl=rl+1: END IF -``` -(y las comparaciones de "Y"/"N" en las líneas 6150/6160) - -**Por qué (este cambio corresponde al esquema de teclado antiguo — ver -nota de "ya no es necesario" más abajo)**: en el momento de portar -`flights.bas`, `zx81sd/runtime/io/keyboard/keyscan.asm` (reescaneo a -mano del **teclado físico del ZX81**, 40 teclas, ya que el SD81 Booster -no tiene teclado Spectrum) solo devolvía minúscula siempre, sin importar -si se pulsaba `SHIFT` o no — `SHIFT+letra` no producía la mayúscula de -esa letra, sino uno de los símbolos clásicos del ZX81 (dos puntos, -comillas, `+`, `-`...). Con ese esquema no existía combinación física -que reprodujera "CAPS SHIFT+S" al estilo Spectrum, y la única -adaptación posible era comparar contra minúscula sin shift. Mismo -motivo, ya resuelto antes por otra vía en `comecoquitos.bas` (ahí el -propio fuente ya comparaba en minúscula, "o","p","q","a","y","n" — -coincidencia de que su autor tecleó así, no algo que tuviéramos que -tocar) y en `snake_sd81.bas` (compara "O"/"o" con `OR`, cubriendo ambos -casos sin necesidad de tocarlo). - -**Ya no es necesario (2026-07-04)**: `keyscan.asm` se rediseñó para que -la pulsación directa de una letra dé minúscula y `SHIFT+letra` dé la -MAYÚSCULA de esa letra (además de `SHIFT+"2"` como CAPS LOCK persistente -y los símbolos clásicos del ZX81 alcanzables con `"."+tecla` desde -`INPUT()`). Con el esquema actual, `INKEY$="S"` ya funcionaría de forma -natural pulsando `SHIFT+S`, exactamente como en un Spectrum — el cambio -de caso de `flights_sd81.bas` documentado arriba fue necesario en su -momento pero ya no lo sería si se portara hoy desde cero. Se deja -constancia aquí en vez de revertir la copia ya probada. Detalle completo -del rediseño en [MAP.md](MAP.md), sección "Esquema de teclado nuevo". - ---- - -## 4. `examples/english/4inarow.bas` — SIN cambios de fuente - -Compila con flags por defecto: - -``` -python -m src.zxbc.zxbc 4inarow.bas --arch zx81sd -o row4.bin -``` - -Sus arrays usan índices 1..8 que caben en base 0 por defecto sin -colisión de slicing (no hace slicing de strings), y sus comparaciones de -tecla ya usan minúsculas ("y"/"n" en los prompts). Ejercita el código de -arcos (`DRAW 8,0,PI`) y `CIRCLE` sin necesitar ningún parche. - ---- - -## 5. `examples/scroll.bas` — SIN cambios de fuente - -Compila con flags por defecto: - -``` -python -m src.zxbc.zxbc scroll.bas --arch zx81sd -o scroll.bin -``` - -El problema no estaba en el ejemplo sino en la librería: `scroll.bas` no -tenía versión zx81sd — se usaba tal cual la de zx48k, cuyas 8 subs -(`ScrollRight/Left/Up/Down` + variantes `*Aligned`) llaman a `$22AC` -(rutina *PIXEL-ADD* de la ROM del Spectrum, inexistente en zx81sd). Se -creó `src/lib/arch/zx81sd/stdlib/scroll.bas`, idéntica salvo sustituir -esas 8 llamadas por `call PIXEL_ADDR` (nuestra propia implementación, -mismo contrato de registros que la ROM — ver [MAP.md](MAP.md)). El -ejemplo en sí no necesitó ningún cambio, igual que 4inarow.bas. - ---- - -## 6. `examples/maskedsprites.bas` → `examples/sd81/maskedsprites_sd81.bas` - -**En proceso — aún no funciona del todo bien.** - -Un cambio de fondo: `WaitForNewFrame` (definida en el propio ejemplo, no -en la librería) reescrita para no depender de interrupciones: - -```diff -- ld de,23672 -- ld c,a ; A = C = minimumNumberofFramesToWaitSinceLastWait -- READ_IFF2 -- ex af,af' -- ei ; interrupts MUST be enabled before HALT -- halt -+ ld de, FRAMES -+ ld c,a ; C = minimumNumberofFramesToWaitSinceLastWait -+ call VSYNC_TICK ; garantiza al menos un frame esperado (sustituye EI+HALT) - wait: - ld a,(de) - sub (hl) - cp c -- jr c,wait -- ld a,(de) -- ld (hl),a -- ex af,af' -- ret pe -- di -- RET -+ jr nc,enough -+ call VSYNC_TICK -+ jr wait -+enough: -+ ld a,(de) -+ ld (hl),a -+ ret -``` - -**Por qué**: `23672` es el contador `FRAMES` de la ROM del Spectrum, -incrementado automáticamente por la interrupción IM1 de 50Hz. El -original hace un `HALT` inicial (espera a la interrupción) y luego un -bucle que confía en que la interrupción lo siga incrementando en segundo -plano mientras el bucle solo *comprueba* sin volver a esperar. En -zx81sd no hay interrupciones (DI permanente; el vector `$0038` es una -trampa `DI;HALT`, no un manejador real) — ese `HALT` no despertaría -nunca. Se sustituye por `VSYNC_TICK` (sondeo del contador de pulsos -VSYNC hardware del SD81 Booster por puerto, la misma rutina que ya usa -`PAUSE`), llamada explícitamente en cada vuelta que haga falta esperar. - -**Actualización — MSFS portado al mapeador de zx81sd (bloque 7)**: el -riesgo de arriba ya no aplica. Se creó -`src/lib/arch/zx81sd/stdlib/cb/maskedsprites.bas` (override completo de -la librería compartida, que sigue intacta): `SetBankPreservingINTs`/ -`GetBankPreservingRegs` reescritas en ASM a mano sobre el puerto `$E7` -(bloque 7, `$E000-$FFFF` — reservado en nuestro mapa de memoria para -"banking de datos, mapas, sprites"), y `MaskedSpritesFileSystemStart` -fija en `$E000` en vez de "lo que quede hasta `$FFFF`" (que asumía RAM -plana Spectrum). El resto de MSFS (cientos de líneas de álgebra de -bloques) es agnóstico de banco/dirección y se copió sin tocar una línea. -Detalle completo, incluido un bug real cazado en el proceso (registros -no preservados si se escriben en BASIC en vez de ASM), en [MAP.md](MAP.md). - -Pese a los fixes de arriba (confirmados con simulación y, en parte, en -emulador/hardware real), la librería en su estado actual **sigue sin -funcionar del todo bien** — trabajo en curso, no dar por cerrado este -ejemplo todavía. - ---- - -## 7. `examples/sd81/pong.bas` — transcripción clásica, un cambio ASM - -No es un ejemplo oficial de `examples/` sino una transcripción clásica -de Pong al estilo Sinclair BASIC (GOSUB/números de línea), añadida -directamente en `examples/sd81/`. Usa comparaciones de tecla ya en -minúscula ("4","q","3","a"), coincidentes con el esquema de teclado -actual sin necesidad de tocar nada — el único cambio fue sustituir la -sincronización de pantalla: - -```diff - ASM -- call VSYNC_TICK ; garantiza al menos un frame esperado (sustituye EI+HALT) -+ call .core.VSYNC_TICK ; garantiza al menos un frame esperado (sustituye EI+HALT) - ; halt ; Avoids screen flickering - END ASM -``` - -**Por qué**: `vsync.asm` envuelve `VSYNC_TICK` en -`push namespace core ... pop namespace`; al llamarlo desde un bloque -`ASM ... END ASM` que no está dentro de ese namespace hace falta el -prefijo completo `.core.VSYNC_TICK` — mismo patrón de error que en -`maskedsprites_sd81.bas` (ver [PRECAUCIONES.md](PRECAUCIONES.md), -sección 5). Sin el prefijo, el compilador da -`Undefined GLOBAL label '.VSYNC_TICK'`. - ---- - -## 8. `examples/sd81/block7test.bas` — sin equivalente oficial, prueba de mapeador - -Programa mínimo (no transcripción de ningún ejemplo) escrito para -confirmar, de forma aislada, que el mapeador de memoria del SD81 Booster -provee almacenamiento independiente por página: escribe patrones -distintos en las páginas 20 y 63 del bloque 7 (`$E000-$FFFF`) vía -`Map()` y verifica que no se pisan entre sí. Sirvió para descartar el -mapeador como causa durante la investigación del bug de MSFS (ver -[MAP.md](MAP.md)). Se conserva como ejemplo de referencia de uso directo -de `Map()`/`MapGet` sobre el bloque 7. - ---- - -## 9. `print42.bas`/`print64.bas` — librerías nuevas (sin cambios de ejemplo) - -`stdlib/print42.bas` y `stdlib/print64.bas` (Britlion) implementan un -`PRINT` de 42/64 columnas dibujando pixel a pixel, en vez de usar el -`PRINT` normal de 32 columnas. La versión zx48k depende de la ROM del -Spectrum en varios sitios: - -- `print42.bas`: `ld hl,$5C7B` (sysvar `UDG`), `ld de,15360` - (`CHARS-256` fijo) y `ld a,(23693)` (sysvar `ATTR_P`). -- `print64.bas`: solo `ld a,(23693)` (`ATTR_P`) — su charset de 4px es - propio, no usa `UDG`/`CHARS`. -- Ambos, además, sitúan la pantalla y los atributos con constantes - **inmediatas** de base fija (`add a,64` / `add a,88`, los bytes altos - de `$4000`/`$5800`) en vez de leer un puntero — funciona en el - Spectrum porque esas direcciones son literales de ROM, pero en zx81sd - `SCREEN_ADDR`/`SCREEN_ATTR_ADDR` son variables (bloque 6, `$C000`/ - `$D800`). - -Se creó `src/lib/arch/zx81sd/stdlib/print42.bas` y `print64.bas` -(overrides completos): sysvars fijas → sus equivalentes zx81sd -(`UDG`/`CHARS`/`ATTR_P`, ver `sysvars.asm`), y las dos constantes de -base de pantalla/atributos se **parchean una vez al entrar en la -función** (automodificación de código sobre la propia instrucción -`ADD A,n`), leyendo el byte alto real de `SCREEN_ADDR`/ -`SCREEN_ATTR_ADDR` en ese momento — igual que hacía la ROM original con -sus propias rutinas, solo que con un valor variable en vez de fijo. - -**Bug real cazado durante el port** (no en el ejemplo, en la librería -misma): un `#include once ` puesto ingenuamente al -principio del fichero (nivel BASIC, fuera de cualquier bloque `ASM`) -producía `error: illegal preprocessor character`, y puesto dentro del -bloque `ASM` de la función compilaba pero colgaba el programa en -tiempo de ejecución (`HALT` en una dirección de código basura). Causa: -si esta librería es la **primera** en incluir `sysvars.asm` en todo el -programa, arrastra tras de sí `bootstrap.asm`/`charset.asm` — y este -último hace `INCBIN "specfont.bin"` (el font completo, no texto -ensamblador) — justo en ese punto del fichero fuente. Puesto a nivel -BASIC, el lexer de BASIC intenta tokenizar bytes binarios de fuente -como si fueran texto (de ahí el "illegal preprocessor character"). -Puesto dentro de la función, el binario del font se emite literalmente -en medio del cuerpo compilado de la función, y la CPU lo "ejecuta" -como si fueran instrucciones en cuanto el flujo cae ahí. Fix: no -incluir `sysvars.asm` en absoluto desde estos ficheros — confían en que -ya esté incluido por el resto del runtime (`CLS`/`PRINT`, que cualquier -programa que use `print42`/`print64` va a usar también), y solo -referencian los símbolos ya namespaced (`.core.CHARS`, etc.). Detalle -completo en [PRECAUCIONES.md](PRECAUCIONES.md) y [MAP.md](MAP.md). - -Verificado por simulación (arnés Python de siempre): las dos cadenas de -prueba (`tests_debug/print4264.bas`, repositorio complementario) se -renderizan legibles píxel a píxel en la posición esperada, sin -escrituras fuera de pantalla/atributos/variables propias del fichero, y -el programa llega a su `__END_PROGRAM`/`HALT` normal. Pendiente de -probar en el emulador/hardware real. - ---- - -## Resumen para el manual - -| Ejemplo | Copia en zx81sd | Cambios de fuente | Flags de compilación | -|---|---|---|---| -| comecoquitos.bas | no hace falta copia si no se toca el original | ninguno | `--string-base 1 --array-base 1` | -| snake_en.bas | `snake_sd81.bas` | 1 línea (dirección UDG) | ninguna especial | -| flights.bas | `flights_sd81.bas` | 3 tipos de cambio (POKE eliminado, 4× PEEK COORDS, 8× case de tecla) | ninguna especial | -| 4inarow.bas | no hace falta copia | ninguno | ninguna especial | -| scroll.bas | no hace falta copia | ninguno (el fix fue en la librería `stdlib/scroll.bas`) | ninguna especial | -| maskedsprites.bas | `maskedsprites_sd81.bas` | `WaitForNewFrame` reescrita (EI+HALT → VSYNC_TICK); librería `cb/maskedsprites.bas` portada al mapeador (bloque 7) — **en proceso, aún no funciona del todo bien** | ninguna especial | -| pong.bas (no oficial) | `pong.bas` en `examples/sd81/` | 1 línea ASM (namespace de VSYNC_TICK) | ninguna especial | -| block7test.bas (no oficial) | `block7test.bas` en `examples/sd81/` | n/a (escrito directamente para zx81sd) | ninguna especial | -| print42.bas/print64.bas (librerías) | no aplica (no son ejemplos) | ninguno — fix fue en `stdlib/print42.bas`/`print64.bas` | ninguna especial | - -Patrón identificado para futuros ejemplos: - -1. Buscar `POKE`/`PEEK` a literales numéricos: casi siempre es una - sysvar del Spectrum que hay que traducir a su dirección zx81sd - equivalente (ver `src/lib/arch/zx81sd/runtime/sysvars.asm`). -2. Comparaciones de `INKEY$` contra mayúsculas de teclas de control - (`"S"`, `"Q"`, etc.): con el esquema de teclado **actual** - (pulsación directa = minúscula, `SHIFT+letra` = mayúscula) esto ya - funciona igual que en un Spectrum y **no hace falta tocar nada** — - el cambio a minúscula documentado arriba para `flights.bas` - corresponde a una versión anterior de `keyscan.asm` (ver nota "Ya no - es necesario" en la sección 3.3) y se mantiene aquí solo como - registro histórico de esa copia ya generada. -3. Código ASM inline que llame a rutinas envueltas en - `push namespace core` (como `VSYNC_TICK`) necesita el prefijo - `.core.` si el bloque `ASM` que lo invoca no está ya dentro de ese - namespace (ver caso de `pong.bas` arriba). -4. Si una librería nueva necesita sysvars propios (`CHARS`/`UDG`/ - `ATTR_P`/`SCREEN_ADDR`/...), **no hacer `#include once ` - dentro del fichero** — solo referenciar los símbolos con el prefijo - `.core.`, confiando en que el resto del runtime (`CLS`/`PRINT`) ya lo - incluyó. Ver caso de `print42.bas`/`print64.bas` arriba y - [PRECAUCIONES.md](PRECAUCIONES.md). diff --git a/src/arch/zx81sd/doc/MAP.md b/src/arch/zx81sd/doc/MAP.md index 2ffd4e9f1..758cb67b6 100644 --- a/src/arch/zx81sd/doc/MAP.md +++ b/src/arch/zx81sd/doc/MAP.md @@ -1,529 +1,533 @@ -# Mapa de tests de depuración (sesión DRAW3 / arco) +# Debug test log (DRAW3 / arc session) -Nota sobre rutas: esta bitácora se escribió originalmente en el -repositorio complementario de pruebas del port (donde los `.bas` citados -vivían todos en un directorio `tests_debug/`). Los ejemplos que se -consideraron suficientemente maduros para publicarse ya están copiados -en este repositorio, en [`examples/sd81/`](../../../../examples/sd81/) +Note on paths: this log was originally written in the companion, +test-only repository for the port (where the `.bas` files mentioned +here all lived in a `tests_debug/` directory). The examples considered +mature enough to publish have already been copied into this +repository, under [`examples/sd81/`](../../../../examples/sd81/) (`flights_sd81.bas`, `snake_sd81.bas`, `maskedsprites_sd81.bas`, -`pong.bas`, `block7test.bas` — ver [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md)). -El resto de fuentes de depuración puntual mencionadas aquí (`diag1-6`, -`t_arc*`, `trig_test`, `heaptest`, `keytest`...) siguen solo en el -repositorio complementario, no en este. +`pong.bas`, `block7test.bas` — see [BASIC_CHANGES.md](BASIC_CHANGES.md)). +The rest of the one-off debugging sources mentioned here (`diag1-6`, +`t_arc*`, `trig_test`, `heaptest`, `keytest`...) only remain in the +companion repository, not this one. -Compilación de cada uno (desde la raíz de este repositorio): +Compiling each one (from the root of this repository): ``` -python -m src.zxbc.zxbc examples\sd81\.bas --arch zx81sd -o .bin -python src\arch\zx81sd\tools\split_sd81.py .bin +python -m src.zxbc.zxbc examples\sd81\.bas --arch zx81sd -o .bin +python src\arch\zx81sd\tools\split_sd81.py .bin ``` -(ver [USO.md](USO.md) para el detalle del empaquetador.) +(see [USAGE.md](USAGE.md) for the packager's detail.) -| Fuente | Prefijo SD81 | Qué prueba | Resultado obtenido | +| Source | SD81 prefix | What it tests | Result obtained | |------------------------|--------------|---------------------------------------------------------------------|---------------------| -| `str_test.bas` | STRTEST | PRINT/STR$ de FLOAT (Fase 3) | OK: 7 / 7.5 / -3.25 / 0 / 123.5 / -0.5 | -| `trig_test.bas` | TRIGTST | SQR/SIN/COS/EXP/LN/ATN con argumentos triviales (Fase 4) | OK: 3 / 1.41421 / 0 / 1 / 2.71828 / 1 / 3.14159 | -| `trig2_test.bas` | TRIG2 | SIN/COS con argumentos NO triviales (1, 1.5708, 3.14159) | OK: 0.8147(4?) / 0.5403 / 0.99999 / -0.99999 / 0 | -| `draw_arc_test.bas` | DRAWARC | DRAW+DRAW+CIRCLE combinados (primer intento de arco) | Solo aparece una línea vertical, CIRCLE nunca se ve | -| `t_line.bas` | TLINE | `DRAW 20,0` (2 args, sin ángulo, sin FP) | OK: línea horizontal | -| `t_circle.bas` | TCIRC | `CIRCLE 60,30,20` sola | OK: círculo correcto | -| `t_arc.bas` | TARC | `DRAW 20,0,3.14159` (arco solo, offset horizontal, 180°) | BUG: línea vertical (no horizontal, no arco) | -| `t_arc2.bas` | TARC2 | `DRAW 30,10,1.5708` (offsets asimétricos, 90°) | BUG: línea vertical con leve inclinación a la derecha | -| `t_arc3.bas` | TARC3 | `PLOT 100,96` + `DRAW 20,0,3.14159` (centrado, con margen) | BUG: patrón de varias líneas en estrella/caóticas desde el centro; una se sale de pantalla hacia el área de atributos | -| `diag1.bas` | DIAG1 | ASM inline: llama a CD-PRMS1 (L247D) directamente con z=40, A=pi/2, e imprime mem-1/mem-3/mem-4/mem-0/nº de líneas | Pantalla en blanco — bug del propio test (`#include` dentro de `ASM` mete código ejecutable en el flujo lineal). Sustituido por diag2 | -| `diag2.bas` | DIAG2 | Igual que diag1 pero sin includes manuales: variables BASIC + PRINT normal | OK: valores exactos esperados → CD-PRMS1 numéricamente correcto | -| `diag4.bas` | DIAG4 | Con `-D DRAW3_DEBUG`: imprime solo N (nº de segmentos capturados en el hook de draw3.asm) | OK: 16 → el hook de captura funciona | -| `diag5.bas` | DIAG5 | diag4 + bucle de recorrido del buffer (2 páginas SD81) | No imprimía; descartado, sustituido por diag6 (1 página) | -| `diag6.bas` | DIAG6/DIAG7 | diag4 + bucle, 1 página. Su volcado de $152D + breakpoints de escritura en $8004 localizaron el bug | OK: 16/16. DIAG7 = idéntico recompilado tras el fix | -| `arcfix.bas` | ARCFIX | Test de referencia idéntico al Spectrum real: `PLOT 100,100` + 2 DRAW arco + CIRCLE | **OK tras el fix**: gancho + círculo, igual que la foto del Spectrum real | - -## Bug de DRAW3 (arco) — RESUELTO - -**Causa raíz** (nada que ver con el calculador FP, cuya matemática resultó -ser exacta): `src/lib/arch/zx81sd/runtime/pixel_addr.asm` destruía el -registro **D** (lo usaba como scratch para V=191−Y). Pero `draw.asm` -(heredado de zx48k) salva la coordenada Y del Bresenham en D' alrededor de -la llamada (`ld d,b / call PIXEL_ADDR / ld b,d`), porque PIXEL-ADD ($22AC) -de la ROM Spectrum preserva DE. Resultado: cada línea con componente -vertical arrancaba internamente con Y=191−y1, corrompiendo COORDS y el -trazado. Las líneas horizontales (t_line) y CIRCLE (que no pasa por ese -camino) salían bien, lo que despistó la investigación inicial. - -**Fix**: PIXEL_ADDR reescrito para usar B como scratch (ya se destruía, -igual que en la ROM) y preservar D y E. Verificado en hardware con ARCFIX -(idéntico al resultado del Spectrum real) y DIAG7. - -**Método de localización**: hook `#ifdef DRAW3_DEBUG` en draw3.asm (activado -con `-D DRAW3_DEBUG`) que captura por cada segmento: |Dy|,|Dx|,signos y -COORDS previas en un buffer (`DRAW3_DEBUG_BUF`); volcado de memoria del -buffer en el debugger de EightyOne + breakpoints de escritura en COORDS -($8004/$8005). El volcado demostró que la posición FP acumulada era -perfecta y que COORDS quedaba mal tras cada línea → el bug estaba en -__DRAW/PIXEL_ADDR, no en fp_calc.asm ni draw3.asm. - -## Ficheros fuente modificados/creados en esta sesión (motor FP + arco) - -- `src/lib/arch/zx81sd/runtime/fp_calc.asm` — Fases 1-5 (motor CALCULATE, - trig/log/exp/sqrt, y ahora STK-TO-A/STK-TO-BC/CD-PRMS1 para el arco). - También se le añadió `#include once ` (bug independiente: - se incluye siempre en todo binario zx81sd, así que debía bastarse a - sí mismo). -- `src/lib/arch/zx81sd/runtime/draw3.asm` — NUEVO. Override de - `zx48k/runtime/draw3.asm` que sustituye las llamadas a direcciones - ROM fijas por las rutinas portadas en `fp_calc.asm`. Incluye - instrumentación de traza tras `#ifdef DRAW3_DEBUG` (inactiva por - defecto; se activa compilando con `-D DRAW3_DEBUG`). -- `src/lib/arch/zx81sd/runtime/pixel_addr.asm` — FIX del bug del arco: - ahora preserva D y E (antes destruía D, rompiendo el Bresenham de - draw.asm en toda línea no horizontal). +| `str_test.bas` | STRTEST | PRINT/STR$ of FLOAT (Phase 3) | OK: 7 / 7.5 / -3.25 / 0 / 123.5 / -0.5 | +| `trig_test.bas` | TRIGTST | SQR/SIN/COS/EXP/LN/ATN with trivial arguments (Phase 4) | OK: 3 / 1.41421 / 0 / 1 / 2.71828 / 1 / 3.14159 | +| `trig2_test.bas` | TRIG2 | SIN/COS with NON-trivial arguments (1, 1.5708, 3.14159) | OK: 0.8147(4?) / 0.5403 / 0.99999 / -0.99999 / 0 | +| `draw_arc_test.bas` | DRAWARC | DRAW+DRAW+CIRCLE combined (first arc attempt) | Only a vertical line appears, CIRCLE never shows | +| `t_line.bas` | TLINE | `DRAW 20,0` (2 args, no angle, no FP) | OK: horizontal line | +| `t_circle.bas` | TCIRC | `CIRCLE 60,30,20` alone | OK: correct circle | +| `t_arc.bas` | TARC | `DRAW 20,0,3.14159` (arc alone, horizontal offset, 180°) | BUG: vertical line (not horizontal, no arc) | +| `t_arc2.bas` | TARC2 | `DRAW 30,10,1.5708` (asymmetric offsets, 90°) | BUG: vertical line with a slight rightward tilt | +| `t_arc3.bas` | TARC3 | `PLOT 100,96` + `DRAW 20,0,3.14159` (centered, with margin) | BUG: chaotic star-like pattern of several lines from the center; one goes off-screen into the attribute area | +| `diag1.bas` | DIAG1 | Inline ASM: calls CD-PRMS1 (L247D) directly with z=40, A=pi/2, and prints mem-1/mem-3/mem-4/mem-0/line count | Blank screen — bug in the test itself (`#include` inside `ASM` puts executable code in the linear flow). Replaced by diag2 | +| `diag2.bas` | DIAG2 | Same as diag1 but without manual includes: BASIC variables + normal PRINT | OK: exact expected values → CD-PRMS1 numerically correct | +| `diag4.bas` | DIAG4 | With `-D DRAW3_DEBUG`: only prints N (number of segments captured by the draw3.asm hook) | OK: 16 → the capture hook works | +| `diag5.bas` | DIAG5 | diag4 + buffer traversal loop (2 SD81 pages) | Didn't print; dropped, replaced by diag6 (1 page) | +| `diag6.bas` | DIAG6/DIAG7 | diag4 + loop, 1 page. Its $152D dump + write breakpoints at $8004 located the bug | OK: 16/16. DIAG7 = identical, recompiled after the fix | +| `arcfix.bas` | ARCFIX | Reference test identical to a real Spectrum: `PLOT 100,100` + 2 arc DRAWs + CIRCLE | **OK after the fix**: hook + circle, matching the real Spectrum photo | + +## DRAW3 (arc) bug — RESOLVED + +**Root cause** (nothing to do with the FP calculator, whose math turned +out to be exact): `src/lib/arch/zx81sd/runtime/pixel_addr.asm` was +destroying register **D** (using it as scratch for V=191−Y). But +`draw.asm` (inherited from zx48k) saves the Bresenham Y coordinate in D +around the call (`ld d,b / call PIXEL_ADDR / ld b,d`), because the +Spectrum ROM's PIXEL-ADD ($22AC) preserves DE. As a result, every line +with a vertical component started internally with Y=191−y1, corrupting +COORDS and the drawing. Horizontal lines (t_line) and CIRCLE (which +doesn't go through that path) came out fine, which threw off the +initial investigation. + +**Fix**: PIXEL_ADDR rewritten to use B as scratch (it was already being +destroyed, same as the ROM) and preserve D and E. Verified on hardware +with ARCFIX (identical to the real Spectrum's result) and DIAG7. + +**Localization method**: a `#ifdef DRAW3_DEBUG` hook in draw3.asm +(enabled with `-D DRAW3_DEBUG`) that captures, for every segment: +|Dy|,|Dx|, signs and the previous COORDS in a buffer +(`DRAW3_DEBUG_BUF`); a memory dump of the buffer in EightyOne's +debugger + write breakpoints on COORDS ($8004/$8005). The dump showed +the accumulated FP position was perfect and that COORDS ended up wrong +after every line → the bug was in __DRAW/PIXEL_ADDR, not in +fp_calc.asm or draw3.asm. + +## Source files modified/created in this session (FP engine + arc) + +- `src/lib/arch/zx81sd/runtime/fp_calc.asm` — Phases 1-5 (the + CALCULATE engine, trig/log/exp/sqrt, and now STK-TO-A/STK-TO-BC/ + CD-PRMS1 for the arc). Also had `#include once ` added + (an unrelated bug: it's always included in every zx81sd binary, so it + had to be self-sufficient). +- `src/lib/arch/zx81sd/runtime/draw3.asm` — NEW. An override of + `zx48k/runtime/draw3.asm` that replaces calls to fixed ROM addresses + with the routines ported in `fp_calc.asm`. Includes trace + instrumentation behind `#ifdef DRAW3_DEBUG` (off by default; enabled + by compiling with `-D DRAW3_DEBUG`). +- `src/lib/arch/zx81sd/runtime/pixel_addr.asm` — FIX for the arc bug: + now preserves D and E (it used to destroy D, breaking draw.asm's + Bresenham on every non-horizontal line). - `src/lib/arch/zx81sd/runtime/fp_tostr.asm`, `printf.asm`, `str.asm` — - de la Fase 3 (PRINT/STR$ de FLOAT), sin cambios en esta sesión. - -## Sonido: BEEP y PLAY (chips AY ZonX del SD81) - -| Fuente | Prefijo SD81 | Qué prueba | Resultado | -|-----------------|--------------|----------------------------------------------------------|-----------| -| `beeptest.bas` | BEEPTS2 | BEEP variable (runtime FP) + BEEP constante (corrección de reloj 13/14 en __BEEPER) | OK: escala + DO/DO' + LA 440 | -| `playtest.bas` | PLAYTS2 | PLAY 3 canales + comparación AY/beeper | OK (¡ojo: notas en minúscula = octava abajo!) | -| `aycal.bas` | AYCAL | Emparejamiento AY vs beeper por semitonos | Sirvió para detectar el desfase | -| `aycal2.bas` | AYCAL2 | Duraciones cronometrables + pareja directa | FFT: beeper 434.5 (correcto, pacing emulador), AY 220 | -| `aycal3.bas` | AYCAL3 | Igual con notas en MAYÚSCULA | OK: unísono 440/440 | - -Lección de la investigación de la "octava fantasma": en el MML de PLAY -(semántica del BASIC 128K), las notas en MINÚSCULA suenan una octava por -debajo de la octava actual. Los tests iniciales usaban minúsculas y el AY -sonaba a 220 Hz *por diseño*. El emulador, la tabla de divisores -(1.625 MHz) y el beeper (3.25 MHz) eran correctos. Se verificó con FFT -sobre la salida de audio grabada. De regalo se corrigió un bug latente -real de EightyOne (el reloj del AY quedaba con el de la tarjeta del -diálogo de hardware en vez del ZonX forzado por el SD81: faltaba llamar a -Sound.InitDevices() tras forzar machine.aytype). - -## Librería MCU (SD81 Booster) — `zx81sd/stdlib/mcu.bas` + `joy.bas` - -| Fuente | Prefijo SD81 | Qué prueba | Resultado | + from Phase 3 (PRINT/STR$ of FLOAT), unchanged in this session. + +## Sound: BEEP and PLAY (SD81's AY ZonX chips) + +| Source | SD81 prefix | What it tests | Result | +|-----------------|--------------|------------------------------------------------------------|-----------| +| `beeptest.bas` | BEEPTS2 | Variable BEEP (FP runtime) + constant BEEP (13/14 clock fix in __BEEPER) | OK: scale + DO/DO' + LA 440 | +| `playtest.bas` | PLAYTS2 | PLAY over 3 channels + AY/beeper comparison | OK (watch out: lowercase notes = one octave down!) | +| `aycal.bas` | AYCAL | AY vs beeper pairing by semitone | Used to detect the offset | +| `aycal2.bas` | AYCAL2 | Timeable durations + direct pairing | FFT: beeper 434.5 (correct, emulator pacing), AY 220 | +| `aycal3.bas` | AYCAL3 | Same with UPPERCASE notes | OK: unison 440/440 | + +Lesson from the "phantom octave" investigation: in PLAY's MML (128K +BASIC semantics), lowercase notes sound one octave below the current +octave. The initial tests used lowercase and the AY was sounding at +220 Hz *by design*. The emulator, the divider table (1.625 MHz) and the +beeper (3.25 MHz) were all correct. Verified with an FFT over recorded +audio output. As a bonus, a real latent EightyOne bug got fixed along +the way (the AY's clock kept the hardware-dialog card's setting instead +of the ZonX one forced by the SD81: it was missing a call to +Sound.InitDevices() after forcing machine.aytype). + +## MCU library (SD81 Booster) — `zx81sd/stdlib/mcu.bas` + `joy.bas` + +| Source | SD81 prefix | What it tests | Result | |----------------|--------------|------------------------------------------------------------|-----------| -| `joytest.bas` | JOYTS2 | `Joy("QAOPM")` (cmd 21) + validación local + eco INKEY$ | OK | -| `mcutest.bas` | MCUTST | VERSION, GET/SETBYTE, PWD, SAVE+LOAD+verificación, DEL, FREE, RTC, BAT, DIR, AY2 por registros, AyPlay | pendiente | -| `maptest.bas` | MAPTST | `Map(bloque,pagina)`/`MapGet` — mapeador $E7: firmas en 2 páginas conmutando el bloque 5 y verificación | OK | -| `exttest.bas` | EXTTST | Extensiones no-MCU: `HexPoke` (*HEX), `MemMove` (*LDIR/*LDDR, stdlib), `StrInv`/`StrBold` (*INV/*BOLD) | OK | -| `ftest.bas` | FTEST | Handles F_*: SAVE, F_OPEN_ZX81 (cmd 58), F_SEEK, F_READ con verificación, F_WRITE+relectura, F_CLOSE, DEL | OK | -| `lstest.bas` | LSTEST | Statements LOAD/SAVE/VERIFY ... CODE nativos → SD (override runtime load.asm/save.asm, cmd 9/10): SAVE+LOAD+verificación, VERIFY ok/corrupto (ERR 26), fichero inexistente (ERR 26) | OK | - -Arquitectura: `mcu.bas` contiene las primitivas del protocolo en ASM -(McuSend/McuRecv/McuSendBlock/McuRecvBlock — las *Block son el camino -crítico de LOAD/SAVE/F_READ/F_WRITE) y los wrappers de todos los -comandos del manual (sistema, ficheros, handles F_*, hardware, voz, -AY2/VGM/PEG, RTC/BAT). Conversión ASCII↔ZX81 automática en los comandos -de texto. `joy.bas` es una capa fina sobre `mcu.bas`. - -Notas de protocolo (extraídas de SD81Booster.cpp del emulador): -- Tras CADA operación en $A7 se espera el cambio del bit 7 de $AF. - NUNCA escribir en $AF (reset del MCU). -- Strings Z80→MCU: byte de longitud + datos (el MCU convierte ZX81→ASCII - salvo comandos "raw": JOY, BINARY_SAY, F_OPEN). -- Streams MCU→Z80 (PWD/DIR/TYPE/FREE_TXT): pedir cada carácter - escribiendo CMD_NEXTCH ($0D); fin = EOT ($6F); después llega el status. -- Respuestas de longitud fija (LOAD, FREE, RTC, BAT, F_READ): ráfaga - de bytes leyendo $A7 con espera de reloj entre cada uno. -- F_OPEN: confirmado en el firmware (COMMANDS.cpp) que el MCU asigna el - handle y lo devuelve (el manual estaba mal y se ha corregido). Añadido - F_OPEN_ZX81 (58) a la librería y al emulador. -- OPENDIR/GETROWLEN/GETROW (16-18) no están emulados en EightyOne: - probarlos solo en hardware real. - -## Test de integración: comecoquitos.bas (ejemplo oficial de zxbasic) - -| Fuente | Prefijo SD81 | Qué prueba | Resultado | +| `joytest.bas` | JOYTS2 | `Joy("QAOPM")` (cmd 21) + local validation + INKEY$ echo | OK | +| `mcutest.bas` | MCUTST | VERSION, GET/SETBYTE, PWD, SAVE+LOAD+verify, DEL, FREE, RTC, BAT, DIR, AY2 by registers, AyPlay | pending | +| `maptest.bas` | MAPTST | `Map(block,page)`/`MapGet` — the $E7 mapper: signatures in 2 pages switching block 5, and verification | OK | +| `exttest.bas` | EXTTST | Non-MCU extensions: `HexPoke` (*HEX), `MemMove` (*LDIR/*LDDR, stdlib), `StrInv`/`StrBold` (*INV/*BOLD) | OK | +| `ftest.bas` | FTEST | F_* handles: SAVE, F_OPEN_ZX81 (cmd 58), F_SEEK, F_READ with verification, F_WRITE+reread, F_CLOSE, DEL | OK | +| `lstest.bas` | LSTEST | Native LOAD/SAVE/VERIFY ... CODE statements → SD (runtime override load.asm/save.asm, cmd 9/10): SAVE+LOAD+verify, VERIFY ok/corrupt (ERR 26), missing file (ERR 26) | OK | + +Architecture: `mcu.bas` contains the protocol primitives in ASM +(McuSend/McuRecv/McuSendBlock/McuRecvBlock — the *Block ones are the +critical path for LOAD/SAVE/F_READ/F_WRITE) and wrappers for every +command in the manual (system, files, F_* handles, hardware, voice, +AY2/VGM/PEG, RTC/BAT). Automatic ASCII↔ZX81 conversion in the text +commands. `joy.bas` is a thin layer over `mcu.bas`. + +Protocol notes (extracted from the emulator's SD81Booster.cpp): +- After EVERY operation on $A7, the change in bit 7 of $AF is expected. + NEVER write to $AF (MCU reset). +- Z80→MCU strings: length byte + data (the MCU converts ZX81→ASCII + except for "raw" commands: JOY, BINARY_SAY, F_OPEN). +- MCU→Z80 streams (PWD/DIR/TYPE/FREE_TXT): request each character by + writing CMD_NEXTCH ($0D); end = EOT ($6F); the status arrives after. +- Fixed-length responses (LOAD, FREE, RTC, BAT, F_READ): a burst of + bytes reading $A7 with a clock wait between each one. +- F_OPEN: confirmed in the firmware (COMMANDS.cpp) that the MCU + assigns the handle and returns it (the manual was wrong and has been + corrected). Added F_OPEN_ZX81 (58) to the library and the emulator. +- OPENDIR/GETROWLEN/GETROW (16-18) aren't emulated in EightyOne: only + test them on real hardware. + +## Integration test: comecoquitos.bas (official zxbasic example) + +| Source | SD81 prefix | What it tests | Result | |---|---|---|---| -| `examples/english/comecoquitos.bas` | COMECO | Juego completo de 1985: FP, strings/slices, arrays, UDGs, bloques gráficos, color/FLASH/BRIGHT, INKEY$, BEEP, RND | OK — idéntico al .tap de Spectrum | -| `fpleak.bas` | FPLEAK | Detector de fugas de la pila FP por bloques (lee $8024 tras cada idiom) | Sirvió para acotar; la "fuga" era corrupción por UDGs | -| `blocktest.bas` | BLKTST | Los 16 gráficos de bloque CHR$(128)-143 | OK tras el fix de PO_GR_1 | - -Tres bugs del runtime cazados con este juego (commits 9903c866 y 9a24059e): -1. UDG apuntaba 128 bytes más allá del final de la fuente (96 chars, no - 256): los POKE USR CHR$ machacaban código del runtime → cuelgue en el - primer uso del calculador. Fix: área dedicada de 21 UDGs. -2. INKEY$ devolvía mayúsculas; el modo L del Spectrum (y los programas de - la época) usan minúsculas. Fix: tabla de keyscan en minúscula. -3. PO_GR_1 (bloques CHR$(128)-143) generaba patrones corruptos (OR al - registro equivocado + cuadrantes izq/der invertidos). Fix: algoritmo - literal de la ROM. - -RECETA para ejemplos clásicos transcritos de Sinclair BASIC: compilar con -`--string-base 1 --array-base 1` (indexación 1-based). Sin ello, las -colisiones por slicing de strings salen desplazadas una posición (no es -un bug del port: en zx48k pasa igual). - -## Heap en $8100 + traps de cinta de EightyOne — RESUELTO (2026-07-04) - -| Fuente | Prefijo SD81 | Qué prueba | Resultado | +| `examples/english/comecoquitos.bas` | COMECO | Complete 1985 game: FP, strings/slices, arrays, UDGs, block graphics, color/FLASH/BRIGHT, INKEY$, BEEP, RND | OK — identical to the Spectrum .tap | +| `fpleak.bas` | FPLEAK | Detector for FP stack leaks by block (reads $8024 after each idiom) | Helped narrow it down; the "leak" was UDG corruption | +| `blocktest.bas` | BLKTST | The 16 block graphics CHR$(128)-143 | OK after the PO_GR_1 fix | + +Three runtime bugs caught with this game (commits 9903c866 and +9a24059e): +1. UDG pointed 128 bytes past the end of the font (96 chars, not 256): + POKE USR CHR$ was clobbering runtime code → hang on the calculator's + first use. Fix: a dedicated 21-UDG area. +2. INKEY$ returned uppercase; the Spectrum's L mode (and the era's + programs) use lowercase. Fix: lowercase keyscan table. +3. PO_GR_1 (blocks CHR$(128)-143) generated corrupt patterns (OR into + the wrong register + swapped left/right quadrants). Fix: the ROM's + literal algorithm. + +RECIPE for classic examples transcribed from Sinclair BASIC: compile +with `--string-base 1 --array-base 1` (1-based indexing). Without it, +string-slicing collisions come out shifted by one position (not a bug +in the port: it happens the same way on zx48k). + +## Heap at $8100 + EightyOne tape traps — RESOLVED (2026-07-04) + +| Source | SD81 prefix | What it tests | Result | |---|---|---|---| -| `tests_debug/heaptest.bas` | HEAPA..HEAPE | Gestor de memoria dinámica (3 fases: crecimiento char a char, REALLOC grandes, STR$ en bucle) con el heap en distintas direcciones | Bisección que aisló el bug | -| `tests_debug/memtest.bas` | MEMTST | R/W patrón dependiente de dirección en $8100-$BFFF (2 pasadas) | OK — descartó el hardware/paginación | -| `tests_debug/inputtest.bas` | INTEST | INPUT() mínimo aislado | Reproducía el cuelgue | -| `examples/sd81/flights_sd81.bas` | FLIGHT | Simulador de vuelo: PEEK COORDS ($8004/5), FP intensivo, INPUT | Adaptación de examples/flights.bas | - -Dos bugs encadenados, cazados el 2026-07-04: - -1. **Compilador (`src/arch/zx81sd/backend/main.py`)**: `heap_size`/`heap_address` - se registraban con `ADD_IF_NOT_DEFINED`, pero el backend Z80 genérico ya - las define antes (4768 / None) → los valores zx81sd ($8100 / 16127) nunca - se aplicaban y el heap acababa inline (DEFS) dentro de la zona ejecutable, - desperdiciando 4768 bytes y limitando el heap. Fix: asignación directa - `OPTIONS.heap_size/heap_address` (la CLI puede seguir sobreescribiendo). - -2. **Emulador (`Eightyone2/src/ZX81/rompatch.cpp`, `PatchTest`)**: los traps - de cinta de la ROM ZX81 ($0207/$02FF/$031E/$0356) se disparaban comparando - PC + `memory[pc]` **plano**, que conserva la ROM aunque el SD81 tenga RAM - mapeada. Con el heap en EQU el runtime baja $12A0 bytes y la división - __DIVU16_FAST aterrizaba en $02FF → el trap de SAVE hacía `DE=1` en mitad - de la división → cociente basura estable → bucle infinito de dígitos en - __PRINTU_LOOP (PUSH AF sin pop) → la pila descendía arrasando el runtime. - Los builds con heap inline eran inmunes de casualidad: las 4 direcciones - trampa caían dentro del bloque DEFS (datos, el PC nunca pasa por ahí). - Fix: `PatchTest` lee el byte con `zx81_PatchPeek()` (mapper-aware). - En hardware real este bug NO existe (no hay traps). - -Metodología que lo resolvió: simulación determinista del binario con la -librería Python `z80` (pip install z80) — diff de integridad de la zona de -código tras cada tramo + breakpoints comparando registros con EightyOne. -El binario era correcto en Z80 puro → la divergencia estaba en el emulador. -Arnés en el scratchpad de la sesión (runsim*.py, reproducible). - -## Scroll de PRINT saltaba a la ROM del Spectrum — RESUELTO (2026-07-04) - -Tercer bug de la cadena de flights.bas (viento=10, dir=100 → HALT): el -`print.asm` de zx81sd conservaba del zx48k el fallback -`__SCROLL_SCR EQU 0DFEh` (rutina CL-SC-ALL de la ROM del Spectrum). En -zx81sd no hay ROM: el primer PRINT que desbordaba la pantalla hacía CALL -a la línea BASIC compilada que casualmente ocupara $0DFE → ejecución -salvaje → RETURN de gosub sacando basura → HALT. Dependía de la entrada -porque el nº de dígitos tecleados movía el cursor: con viento "1"/"0" el -texto no llegaba a desbordar; con "10"/"100" sí. Ningún test anterior lo -pilló porque todos usan PRINT AT (nunca scroll). - -Fix: la implementación por búfer (`__ZXB_ENABLE_BUFFER_SCROLL`, scrollea -vía SCREEN_ADDR/SCREEN_ATTR_ADDR) es ahora la rama única de __SCROLL_SCR -en zx81sd/runtime/print.asm. Verificado con el simulador Python + teclado -scriptado: el bucle principal del juego mantiene SP estable durante miles -de pasos. Ojo futuro: revisar cualquier otro EQU/CALL a direcciones -absolutas de ROM Spectrum al portar ficheros del zx48k (grep hecho: -no queda ninguno en el runtime zx81sd). - -## Esquema de teclado nuevo: mayúsculas, CAPS LOCK y símbolos — 2026-07-04 - -| Fuente | Prefijo SD81 | Qué prueba | Resultado | +| `tests_debug/heaptest.bas` | HEAPA..HEAPE | Dynamic memory manager (3 phases: char-by-char growth, large REALLOCs, STR$ in a loop) with the heap at different addresses | Bisection that isolated the bug | +| `tests_debug/memtest.bas` | MEMTST | R/W pattern dependent on address in $8100-$BFFF (2 passes) | OK — ruled out hardware/paging | +| `tests_debug/inputtest.bas` | INTEST | Minimal, isolated INPUT() | Reproduced the hang | +| `examples/sd81/flights_sd81.bas` | FLIGHT | Flight simulator: PEEK COORDS ($8004/5), FP-intensive, INPUT | Adaptation of examples/flights.bas | + +Two chained bugs, caught on 2026-07-04: + +1. **Compiler (`src/arch/zx81sd/backend/main.py`)**: `heap_size`/ + `heap_address` were registered with `ADD_IF_NOT_DEFINED`, but the + generic Z80 backend already defines them earlier (4768 / None) → + zx81sd's values ($8100 / 16127) were never applied and the heap + ended up inline (DEFS) inside the executable area, wasting 4768 + bytes and limiting the heap. Fix: direct assignment of + `OPTIONS.heap_size/heap_address` (the CLI can still override it). + +2. **Emulator (`Eightyone2/src/ZX81/rompatch.cpp`, `PatchTest`)**: the + ZX81 ROM's tape traps ($0207/$02FF/$031E/$0356) were triggered by + comparing PC + a **flat** `memory[pc]` read, which keeps reading the + ROM even though the SD81 has RAM mapped. With the heap in EQU, the + runtime moves $12A0 bytes down and the __DIVU16_FAST division lands + on $02FF → the SAVE trap sets `DE=1` mid-division → stable garbage + quotient → an infinite digit loop in __PRINTU_LOOP (PUSH AF with no + matching pop) → the stack descends, wiping out the runtime. Builds + with an inline heap were immune by coincidence: the 4 trap addresses + fell inside the DEFS block (data, the PC never passes through + there). Fix: `PatchTest` reads the byte with `zx81_PatchPeek()` + (mapper-aware). This bug does NOT exist on real hardware (there are + no traps). + +Methodology that solved it: deterministic simulation of the binary +with Python's `z80` library (pip install z80) — code-area integrity +diff after each stretch + breakpoints comparing registers against +EightyOne. The binary was correct in pure Z80 → the divergence was in +the emulator. Harness in the session's scratchpad (runsim*.py, +reproducible). + +## PRINT scroll jumping into the Spectrum ROM — RESOLVED (2026-07-04) + +Third bug in the flights.bas chain (wind=10, dir=100 → HALT): zx81sd's +`print.asm` had kept, from zx48k, the fallback +`__SCROLL_SCR EQU 0DFEh` (the Spectrum ROM's CL-SC-ALL routine). On +zx81sd there's no ROM: the first PRINT that overflowed the screen did a +CALL to whatever compiled BASIC line happened to occupy $0DFE → wild +execution → a gosub RETURN popping garbage → HALT. It depended on the +input because the number of digits typed moved the cursor: with wind +"1"/"0" the text never overflowed; with "10"/"100" it did. No previous +test caught this because they all use PRINT AT (never scroll). + +Fix: the buffer-based implementation (`__ZXB_ENABLE_BUFFER_SCROLL`, +scrolls via SCREEN_ADDR/SCREEN_ATTR_ADDR) is now the only branch of +__SCROLL_SCR in zx81sd/runtime/print.asm. Verified with the Python +simulator + scripted keyboard: the game's main loop keeps SP stable +over thousands of steps. Future note: check for any other EQU/CALL to +absolute Spectrum ROM addresses when porting files from zx48k (grep +already done: none remain in the zx81sd runtime). + +## New keyboard scheme: uppercase, CAPS LOCK and symbols — 2026-07-04 + +| Source | SD81 prefix | What it tests | Result | |---|---|---|---| -| `tests_debug/keytest.bas` | KEYTST | INKEY$ interactivo: imprime código ASCII + carácter de cada tecla | Verificado por simulación exhaustiva (ver abajo); pendiente de probar a mano en el emulador/hardware | - -El teclado físico del ZX81 no distingue mayúscula/minúscula por tecla -(ver [[zx81sd-keyboard-case]]): SHIFT+letra da un símbolo, no la -mayúscula de esa letra. Hasta ahora `keyscan.asm` solo devolvía -minúsculas siempre. Nuevo esquema (`src/lib/arch/zx81sd/runtime/io/ -keyboard/keyscan.asm`, reescrito de raíz): - -- Sin modificador: minúscula (igual que antes — comecoquitos, snake, - flights, row4 siguen funcionando sin tocar una línea, porque ninguno - usa SHIFT). -- `SHIFT + letra`: MAYÚSCULA de esa letra. -- `SHIFT + "2"`: conmuta CAPS LOCK persistente (mudo, no imprime nada). -- CAPS LOCK activo: minúscula pasa a mayúscula; SHIFT sigue dando - mayúscula igual (es un OR, no hay interacción, decisión tomada con el - usuario). -- `"."` sola: `.` -- `SHIFT + "."`: `,` (igual que en el ZX81 real). -- `"." + otra tecla`: el símbolo impreso en el teclado del ZX81 para esa - tecla (`:` con Z, `)` con O, RUBOUT con 0, etc. — la vieja tabla SHIFT - del ZX81, ahora alcanzable con "." en vez de con SHIFT, ya que SHIFT - se ha redefinido para dar mayúsculas). - -Requirió reescribir el escaneo: antes se paraba en la primera fila con -algo pulsado (bastaba con una tecla a la vez). Ahora hacen falta dos -lecturas de puerto dedicadas para SHIFT (fila 0) y "." (fila 7, columna -1), más un escaneo de las demás filas buscando una tercera tecla -excluyendo esas dos posiciones (rutina `FIND_OTHER`). El combo -`SHIFT+"2"` usa un byte de estado persistente con detección de flanco -para no conmutar varias veces mientras se mantiene pulsado. - -Verificado con un arnés de simulación Python (`z80`, ver metodología ya -usada para los bugs del heap): se llama a `__ZX81SD_KEYSCAN` directamente -inyectando por el callback de E/S los bits de fila exactos de cada -combinación (Z sola, SHIFT+Z, "."+Z, SHIFT+2 mantenido 4 polls seguidos, -etc.), sin pasar por la complejidad de un teclado real. Los offsets de -las tablas y del estado (`_KBD_*`, todos LOCAL al PROC, no aparecen en el -`.map`) se localizaron buscando el patrón de bytes de `UNSHIFT_TABLE` -("zxcvasdfg") en el binario y calculando el resto por desplazamiento fijo -(cada tabla ocupa 39 bytes). Los 12 casos de la tabla de diseño -coincidieron exactamente, incluido el debounce del CAPS LOCK. - -### Corrección 2026-07-04: el modificador "." se movió de keyscan a input.bas - -Al probarlo en el emulador, el usuario detectó que `"."+tecla` (pensado -para dar el símbolo del ZX81 pulsando ambas a la vez, como SHIFT+letra) -era impracticable desde `INPUT`: `PRIVATEInputWaitKey` compromete la -tecla `"."` en cuanto la detecta sola, sin dar tiempo a que la segunda -tecla llegue de verdad a la vez (a diferencia de SHIFT+letra, que sí se -puede sostener cómodamente con la otra mano). Diagnóstico correcto del -usuario: *"la gestión de los símbolos no debe ir en el keyscan sino en -INPUT.bas"*. - -Fix: `keyscan.asm` ahora trata `"."` como una tecla más — sin -modificador da `.`, con SHIFT da `,` (igual que el ZX81 real), sin -ninguna lógica de "tercera tecla" para el punto (se quitó por completo -la exclusión de la fila 7 en `FIND_OTHER`, ya no hace falta). Las tablas -`UNSHIFT_TABLE`/`SYMBOL_TABLE`/`CAPS_TABLE` se promovieron de `LOCAL` a -ámbito de fichero (prefijo `__ZX81SD_`) y se añadió una rutina nueva, -`__ZX81SD_SYMBOL_FOR(char)`, que hace la búsqueda inversa -UNSHIFT_TABLE→SYMBOL_TABLE dado un carácter ya decodificado. - -La composición de símbolos ahora vive en `stdlib/input.bas` como una -"tecla muerta" secuencial: al leer `"."`, la función `input()` lee la -SIGUIENTE tecla por separado (sin exigir simultaneidad) y llama a -`PRIVATEInputSymbolFor()`; si hay símbolo, lo añade; si no, añade el -punto literal y procesa la segunda tecla con normalidad (DEL, ENTER, o -un carácter más). - -Bug propio cazado durante la implementación (antes de que el usuario lo -viera): `"."` + `"0"` resuelve a RUBOUT (12) vía `SYMBOL_TABLE` (es el -símbolo real que el ZX81 imprime sobre la tecla "0"), lo que borraría el -carácter anterior al escribir cualquier decimal terminado en ".0" (muy -habitual: "3.0", "10.0"...). Se excluyó ese valor explícitamente en -`input.bas` — RUBOUT ya se alcanza sin ambigüedad con SHIFT+0 (sí es -cómodo de sostener a la vez). Verificado con simulación de programa -completo (tecleo scriptado "1",".","0",ENTER → `a$="1.0"` correctamente, -y "."," o",ENTER → `a$=")"`), no solo con la función aislada. - -`tests_debug/keytest.bas` (KEYTST) sigue siendo el tester interactivo de -`INKEY$`; `tests_debug/inputtest.bas` (INTEST) es el mismo mini test de -`INPUT()` de antes, ahora también sirve para probar la composición de -símbolos a mano. - -### Refinamiento 2026-07-04: punto dos veces seguidas = punto literal - -Con el diseño anterior, `"."` + una letra con símbolo asociado (p.ej. Z → -`:`) siempre resolvía a ese símbolo — no había forma de escribir -literalmente un punto seguido de esa letra. Pedido del usuario: pulsar -`"."` dos veces seguidas debe confirmar el primer punto como literal -(la coma redundante que antes salía de `"."+"."`, vía `SYMBOL_TABLE`, ya -no hace falta — sale directamente y sin ambigüedad con `SHIFT+"."`), y la -tecla que venga después se lee como una pulsación nueva sin combinar. -Así, para escribir ".Z" se teclea "." "." "Z". - -Implementado en `input.bas`: si la segunda tecla leída tras un "." es -también un ".", se añade el punto, se descarta la segunda pulsación (no -imprime nada por sí misma, `LastK=0`) y el bucle vuelve a leer una tecla -fresca. Verificado con la misma simulación de programa completo: -`"."+"."` → `a$="."`; `"."+"."+"z"` → `a$=".z"` (sin formar el símbolo -`:`). - -## scroll.bas — RESUELTO 2026-07-04 (librería nueva, ejemplo sin cambios) - -| Fuente | Prefijo SD81 | Qué prueba | Resultado | +| `tests_debug/keytest.bas` | KEYTST | Interactive INKEY$: prints the ASCII code + character of every key | Verified by exhaustive simulation (see below); pending manual testing on the emulator/hardware | + +The ZX81's physical keyboard doesn't distinguish upper/lowercase per +key: SHIFT+letter gives a symbol, not the uppercase of that letter. +Until now `keyscan.asm` only ever returned lowercase. New scheme +(`src/lib/arch/zx81sd/runtime/io/keyboard/keyscan.asm`, rewritten from +scratch): + +- No modifier: lowercase (same as before — comecoquitos, snake, + flights, row4 keep working without touching a line, because none of + them use SHIFT). +- `SHIFT + letter`: UPPERCASE of that letter. +- `SHIFT + "2"`: toggles a persistent CAPS LOCK (silent, prints + nothing). +- CAPS LOCK active: lowercase becomes uppercase; SHIFT still gives + uppercase the same way (it's an OR, no interaction — decision made + with the user). +- `"."` alone: `.` +- `SHIFT + "."`: `,` (same as a real ZX81). +- `"." + another key`: the symbol printed on the ZX81 keyboard for that + key (`:` with Z, `)` with O, RUBOUT with 0, etc. — the ZX81's old + SHIFT table, now reachable with "." instead of SHIFT, since SHIFT has + been redefined to give uppercase). + +Required rewriting the scan: it used to stop at the first row with +anything pressed (one key at a time was enough). Now two dedicated port +reads are needed for SHIFT (row 0) and "." (row 7, column 1), plus a +scan of the other rows looking for a third key, excluding those two +positions (`FIND_OTHER` routine). The `SHIFT+"2"` combo uses a +persistent state byte with edge detection so it doesn't toggle +repeatedly while held down. + +Verified with a Python simulation harness (`z80`, same methodology +already used for the heap bugs): `__ZX81SD_KEYSCAN` is called directly, +injecting the exact row bits of each combination through the I/O +callback (Z alone, SHIFT+Z, "."+Z, SHIFT+2 held for 4 consecutive +polls, etc.), without dealing with the complexity of a real keyboard. +The table and state offsets (`_KBD_*`, all LOCAL to the PROC, don't +show up in the `.map`) were located by searching for +`UNSHIFT_TABLE`'s byte pattern ("zxcvasdfg") in the binary and +computing the rest by fixed offset (each table takes 39 bytes). All 12 +cases in the design table matched exactly, including the CAPS LOCK +debounce. + +### 2026-07-04 fix: the "." modifier moved from keyscan to input.bas + +When testing it on the emulator, the user found that `"."+key` +(intended to give the ZX81 symbol by pressing both at once, like +SHIFT+letter) was impractical from `INPUT`: `PRIVATEInputWaitKey` +commits to the `"."` key as soon as it detects it alone, without giving +the second key time to actually arrive at the same time (unlike +SHIFT+letter, which can comfortably be held with the other hand). The +user's correct diagnosis: *"symbol handling shouldn't live in keyscan +but in INPUT.bas"*. + +Fix: `keyscan.asm` now treats `"."` like any other key — unshifted +gives `.`, with SHIFT gives `,` (same as a real ZX81), with none of the +"third key" logic for the dot (the row-7 exclusion in `FIND_OTHER` was +removed entirely, no longer needed). The `UNSHIFT_TABLE`/ +`SYMBOL_TABLE`/`CAPS_TABLE` tables were promoted from `LOCAL` to file +scope (`__ZX81SD_` prefix) and a new routine was added, +`__ZX81SD_SYMBOL_FOR(char)`, which does the reverse +UNSHIFT_TABLE→SYMBOL_TABLE lookup given an already-decoded character. + +Symbol composition now lives in `stdlib/input.bas` as a sequential +"dead key": on reading `"."`, the `input()` function reads the NEXT key +separately (with no simultaneity requirement) and calls +`PRIVATEInputSymbolFor()`; if there's a symbol, it's appended; if not, +the literal dot is appended and the second key is processed normally +(DEL, ENTER, or one more character). + +A bug of my own caught during the implementation (before the user ever +saw it): `"."` + `"0"` resolves to RUBOUT (12) via `SYMBOL_TABLE` (it's +the actual symbol the ZX81 prints over the "0" key), which would delete +the previous character when typing any decimal ending in ".0" (very +common: "3.0", "10.0"...). That value was explicitly excluded in +`input.bas` — RUBOUT is already reachable unambiguously with SHIFT+0 +(which is comfortable to hold at the same time). Verified with a +full-program simulation (scripted typing "1",".","0",ENTER → +`a$="1.0"` correctly, and "."," o",ENTER → `a$=")"`), not just the +isolated function. + +`tests_debug/keytest.bas` (KEYTST) is still the interactive `INKEY$` +tester; `tests_debug/inputtest.bas` (INTEST) is the same old mini +`INPUT()` test, now also useful for testing symbol composition by hand. + +### 2026-07-04 refinement: two dots in a row = a literal dot + +With the previous design, `"."` + a letter with an associated symbol +(e.g. Z → `:`) always resolved to that symbol — there was no way to +literally write a dot followed by that letter. User request: pressing +`"."` twice in a row should confirm the first dot as literal (the +redundant comma that used to come out of `"."+"."`, via +`SYMBOL_TABLE`, is no longer needed — it's already reachable directly +and unambiguously with `SHIFT+"."`), and whatever key comes after is +read as a fresh keypress with no combination. So, to write ".Z" you +type "." "." "Z". + +Implemented in `input.bas`: if the second key read after a "." is also +a ".", the dot is appended, the second keypress is discarded (prints +nothing by itself, `LastK=0`) and the loop goes back to reading a fresh +key. Verified with the same full-program simulation: +`"."+"."` → `a$="."`; `"."+"."+"z"` → `a$=".z"` (without forming the +`:` symbol). + +## scroll.bas — RESOLVED 2026-07-04 (new library, example unchanged) + +| Source | SD81 prefix | What it tests | Result | |---|---|---|---| -| `examples/scroll.bas` | SCROLL | Los 4 scrolls pixel-a-pixel (Right/Down/Left/Up) sobre una ventana de 60×60 px, 30 vueltas | Simulado 1200M ticks sin HALT/RST38; pendiente de ver en el emulador (el ejemplo hace 1920 scrolls de hasta 100×100 px, tarda un rato) | - -`src/lib/arch/zx48k/stdlib/scroll.bas` no tenía override en zx81sd — se -usaba la versión de zx48k tal cual, y las 8 subs (`ScrollRight/Left/Up/ -Down` + sus variantes `*Aligned`) llaman todas a `call 22ACh`, la rutina -*PIXEL-ADD* de la ROM del Spectrum. En zx81sd no hay ROM mapeada: esa -dirección cae en pleno código compilado del programa, y el HALT -reportado (`RST 38` en la traza) era justo el байте que hubiera ahí por -casualidad. - -Fix: `src/lib/arch/zx81sd/stdlib/scroll.bas`, copia idéntica salvo las 8 -llamadas a `$22AC` sustituidas por `call PIXEL_ADDR` (nuestra propia -rutina, `runtime/pixel_addr.asm`, ya usada por `plot.asm`/`draw.asm` — -ver [[zx81sd-pixel-addr-contract]]). El contrato de registros es -IDÉNTICO al de la ROM (A=191, B=Y, C=X → HL=offset, A=X AND 7, destruye -B, preserva D/E), así que no hizo falta tocar ni una línea del cuerpo de -los bucles de scroll, solo el punto de llamada. `SP.PixelDown`/ -`SP.PixelUp` (de zx48k/runtime/SP/) no necesitaron copia: son aritmética -pura sobre `SCREEN_ADDR`, sin ROM, y ya funcionaban igual en zx81sd (se -resuelven por el mecanismo normal de fallback a zx48k cuando no hay -override). - -`examples/scroll.bas` no necesitó ningún cambio de fuente — como -`4inarow.bas`, el problema era enteramente de la librería, no del -programa. Añadido a `CAMBIOS_BASIC.md` con esa misma nota. - -## maskedsprites.bas — RESUELTO 2026-07-04 (cambio de fuente, no de librería) - -| Fuente | Prefijo SD81 | Qué prueba | Resultado | +| `examples/scroll.bas` | SCROLL | The 4 pixel-by-pixel scrolls (Right/Down/Left/Up) over a 60×60 px window, 30 rounds | Simulated 1200M ticks with no HALT/RST38; pending viewing on the emulator (the example does 1920 scrolls of up to 100×100 px, takes a while) | + +`src/lib/arch/zx48k/stdlib/scroll.bas` had no zx81sd override — the +zx48k version was used as-is, and its 8 subs (`ScrollRight/Left/Up/ +Down` + their `*Aligned` variants) all call `call 22ACh`, the Spectrum +ROM's *PIXEL-ADD* routine. On zx81sd there's no ROM mapped: that +address lands in the middle of the program's compiled code, and the +reported HALT (`RST 38` in the trace) was just whatever byte happened +to be there. + +Fix: `src/lib/arch/zx81sd/stdlib/scroll.bas`, an identical copy except +the 8 calls to `$22AC` are replaced with `call PIXEL_ADDR` (our own +routine, `runtime/pixel_addr.asm`, already used by `plot.asm`/ +`draw.asm`). The register contract is IDENTICAL to the ROM's (A=191, +B=Y, C=X → HL=offset, A=X AND 7, destroys B, preserves D/E), so not a +single line of the scroll loop bodies needed touching, only the call +site. `SP.PixelDown`/`SP.PixelUp` (from zx48k/runtime/SP/) didn't need +copying: they're pure arithmetic over `SCREEN_ADDR`, no ROM involved, +and already worked the same way on zx81sd (resolved through the normal +zx48k fallback mechanism when there's no override). + +`examples/scroll.bas` itself needed no source change — like +`4inarow.bas`, the problem was entirely in the library, not the +program. Added to `BASIC_CHANGES.md` with the same note. + +## maskedsprites.bas — RESOLVED 2026-07-04 (source change, not a library one) + +| Source | SD81 prefix | What it tests | Result | |---|---|---|---| -| `examples/maskedsprites.bas` → `examples/sd81/maskedsprites_sd81.bas` | MASKED | Sprites enmascarados (AND+OR) con MSFS, 10 sprites animados | Simulado 1000M ticks sin HALT/RST38/escritura ilegal; PC avanza por un rango amplio de direcciones (no atascado) | - -A diferencia de `scroll.bas`, aquí el problema SÍ estaba en el propio -ejemplo (`WaitForNewFrame`, definida directamente en `examples/ -maskedsprites.bas`, no en la librería `cb/maskedsprites.bas`): hace -`EI` + `HALT` esperando la interrupción IM1 de 50Hz de la ROM del -Spectrum, comparando contra el contador `FRAMES` de la ROM en la -dirección absoluta `23672`. En zx81sd las interrupciones están -permanentemente deshabilitadas (todo el runtime corre con `DI`; el -vector `$0038` es solo una trampa `DI;HALT`, no un manejador real) — ese -`HALT` no despierta nunca. Confirmado con la traza: el simulador se -quedaba con `m.halted=True` exactamente en el `HALT` de `WaitForNewFrame` -tras ~31M ticks. - -Fix en `examples/sd81/maskedsprites_sd81.bas`: `WaitForNewFrame` reescrita -para usar `VSYNC_TICK` (`runtime/vsync.asm`, ya usada por `PAUSE`) en vez -de `EI+HALT` — sondea por puerto ($AFh) el contador de pulsos VSYNC -hardware del SD81 Booster, sin depender de interrupciones. El algoritmo -original hacía UN `HALT` inicial y luego un bucle que comprobaba -`FRAMES` SIN esperar de nuevo (confiaba en que la interrupción lo -siguiera incrementando en segundo plano); como en zx81sd nada lo -incrementa solo, el bucle llama a `VSYNC_TICK` explícitamente en cada -vuelta que le falte. `GetInterruptStatusInBorder` se dejó intacta (no se -llama nunca en el bucle principal, solo aparece comentada — se mantiene -únicamente para que la comprobación de compilación del final del fichero -no falle por "función no usada"). - -### Actualización 2026-07-04: MSFS portado de verdad al mapeador (bloque 7) - -El riesgo de `$5B5C`/`$7FFD` de arriba **ya no aplica**: a petición del -usuario ("¿por qué no usamos el bloque 7 que tenemos para bancos?") se -creó `src/lib/arch/zx81sd/stdlib/cb/maskedsprites.bas`, override -completo de la librería compartida (que sigue intacta, regla de -Boriel). Diseño (ver también `CAMBIOS_BASIC.md`): - -- **Hallazgo clave**: las funciones de MSFS (`RegisterSpriteImageInMSFS`, - `FindFirstUnusedBlockInMSFS`, etc.) son agnósticas de banco/dirección - — solo llaman a `GetBankPreservingRegs`/`SetBankPreservingINTs` y - leen/escriben la variable BASIC `MaskedSpritesFileSystemStart`. - Reescribiendo esas dos primitivas (usando el puerto `$E7` sobre el - **bloque 7**, `$E000-$FFFF` — reservado en nuestro mapa de memoria - justo para "banking de datos, mapas, sprites") y el cálculo de esa - dirección en `InitMaskedSpritesFileSystem()` (fija en `$E000` en vez - de "lo que quede hasta `$FFFF`", que asumía RAM plana Spectrum), el - resto del fichero (cientos de líneas de álgebra de bloques/bitmap) se - copió literalmente sin tocar una línea. -- `CheckMemoryPaging()` devuelve `0` (honesto: zx81sd no tiene doble - pantalla visible al estilo banco 5/7 del Spectrum) sin afectar a - MSFS, porque las funciones de MSFS no consultan esa función para - decidir si usar el banco — lo hacen incondicionalmente. +| `examples/maskedsprites.bas` → `examples/sd81/maskedsprites_sd81.bas` | MASKED | Masked sprites (AND+OR) with MSFS, 10 animated sprites | Simulated 1000M ticks with no HALT/RST38/illegal write; PC advances across a wide range of addresses (not stuck) | + +Unlike `scroll.bas`, here the problem WAS in the example itself +(`WaitForNewFrame`, defined directly in `examples/maskedsprites.bas`, +not in the `cb/maskedsprites.bas` library): it does `EI` + `HALT` +waiting for the Spectrum ROM's 50Hz IM1 interrupt, comparing against +the ROM's `FRAMES` counter at absolute address `23672`. On zx81sd +interrupts are permanently disabled (the whole runtime runs with `DI`; +the `$0038` vector is only a `DI;HALT` trap, not a real handler) — that +`HALT` never wakes up. Confirmed by the trace: the simulator ended up +with `m.halted=True` exactly at `WaitForNewFrame`'s `HALT` after ~31M +ticks. + +Fix in `examples/sd81/maskedsprites_sd81.bas`: `WaitForNewFrame` +rewritten to use `VSYNC_TICK` (`runtime/vsync.asm`, already used by +`PAUSE`) instead of `EI+HALT` — polls the SD81 Booster's hardware +VSYNC pulse counter through a port ($AFh), with no dependency on +interrupts. The original algorithm did ONE initial `HALT` and then a +loop that checked `FRAMES` WITHOUT waiting again (trusting the +interrupt to keep incrementing it in the background); since nothing +increments it on its own on zx81sd, the loop calls `VSYNC_TICK` +explicitly on every round it still needs to wait. +`GetInterruptStatusInBorder` was left untouched (never called in the +main loop, only appears commented out — kept only so the end-of-file +compile check doesn't fail with "unused function"). + +### 2026-07-04 update: MSFS genuinely ported to the mapper (block 7) + +The `$5B5C`/`$7FFD` risk from above **no longer applies**: at the +user's request ("why don't we use the block 7 we have for banking?"), +`src/lib/arch/zx81sd/stdlib/cb/maskedsprites.bas` was created, a full +override of the shared library (which stays untouched, Boriel's rule). +Design (see also `BASIC_CHANGES.md`): + +- **Key finding**: MSFS's functions (`RegisterSpriteImageInMSFS`, + `FindFirstUnusedBlockInMSFS`, etc.) are bank/address-agnostic — they + only call `GetBankPreservingRegs`/`SetBankPreservingINTs` and read/ + write the BASIC variable `MaskedSpritesFileSystemStart`. By + rewriting those two primitives (using port `$E7` over **block 7**, + `$E000-$FFFF` — reserved in our memory map exactly for "data + banking, maps, sprites") and that address's calculation in + `InitMaskedSpritesFileSystem()` (fixed at `$E000` instead of + "whatever's left up to `$FFFF`", which assumed flat Spectrum RAM), + the rest of the file (hundreds of lines of block/bitmap arithmetic) + was copied literally without touching a line. +- `CheckMemoryPaging()` returns `0` (honest: zx81sd has no Spectrum + bank-5/7-style dual visible screen) with no effect on MSFS, because + MSFS's functions don't consult that function to decide whether to + use the bank — they always do it unconditionally. - `SetVisibleScreen`/`GetVisibleScreen`/`ToggleVisibleScreen`/ `CopyScreen5ToScreen7`/`CopyScreen7ToScreen5`/`SetDrawingScreen5`/ - `SetDrawingScreen7`/`ToggleDrawingScreen` → stubs seguros (doble - buffer de pantalla real no está cubierto; código muerto en este - ejemplo dado que `memoryPaging=0`, pero ya no tocan `$5B5C`/`$7FFD` - por si alguien los llama directamente en el futuro). - -**Bug real encontrado durante la implementación** (no por el usuario, -cazado con el propio arnés de simulación): mi primer intento escribió -`SetBankPreservingINTs`/`GetBankPreservingRegs` en BASIC plano en vez de -ASM a mano. Rompía el contrato de registros documentado en el propio -fichero original ("Preserves: D, E, H, L") que el código ASM de -`RegisterSpriteImageInMSFS` y compañía da por hecho (por ejemplo, para -no perder `spriteImageAddr`, que llega en HL) — una función BASIC -compilada usa registros libremente por dentro sin ninguna garantía de -preservarlos. Resultado: los 6 sprites de prueba se registraban todos en -la MISMA dirección (`$0C07`) en vez de direcciones distintas. Se -reescribieron ambas primitivas en ASM a mano, con el mismo contrato -exacto que el original (solo tocan A, B, C). - -Verificado con simulación: las 6 direcciones de registro (`regHero0`, -`regFoe00`, `regFoe20-23`) salen correlativas cada 96 bytes exactos -(`$E010, $E070, $E0D0, $E130, $E190, $E1F0`, coincidiendo con -`$E000+n*96+16`), sin disparar `STOP`, y el bucle principal -(`WaitForNewFrame`) se alcanza repetidamente sin cuelgue tras 1000 -millones de ticks de simulación sin `HALT`/escritura ilegal. - -Limitación conocida del simulador Python usado en esta sesión: no -modela el mapeador de memoria (los `OUT` a `$E7` son no-op en la -simulación, toda la RAM se trata como plana) — no puede validar que el -intercambio de página *físico* funcione de verdad, solo que la lógica -Z80 es correcta asumiendo que sí. La validación definitiva es en el -emulador/hardware real. - -### Segunda vuelta 2026-07-04: seguía yéndose a HALT en hardware real - -Con el fix de arriba ya compilado, en el emulador real seguía disparando -`__STOP` (mismo síntoma: `RegisterSpriteImageInMSFS` devuelve 0). Traza -confirmó que el problema estaba en `SetBankPreservingINTs`, que hacía el -`OUT` al puerto `$E7` a mano en vez de llamar a `Map()` (mcu.bas): -escribía `A=7` (bloque, sin combinar con la página) con `B=página`. Mi -hipótesis inicial fue que el hardware podía estar en modo simple (donde -el byte de datos debe llevar página Y bloque combinados, -`(página AND 31)<<3 | bloque`, y con solo `A=7` se interpretaría como -página=0) — **el usuario corrigió esto**: el cargador SD81 deja el -mapeador en modo completo desde la línea `LOAD *MAP 7,63`, hasta el -siguiente reset, así que esa explicación concreta no cuadra (en modo -completo solo importan los 3 bits bajos de A, iguales en ambas -versiones). La causa exacta seguía sin confirmarse en el momento de -escribir esto. - -Se cambió `SetBankPreservingINTs` para llamar a `Map()` (código ya -probado en otros contextos) en vez de repetir la lógica del puerto a -mano, preservando D,E,H,L alrededor de la llamada con `push`/`pop` -manuales (`Map()` en sí no preserva nada). Tuvo un efecto secundario: -`Map()` dejó de estar referenciada desde BASIC en ningún sitio (solo -desde ASM a mano), y el eliminador de código muerto del compilador la -quitó del binario → `Undefined GLOBAL label '._Map'`. Se resolvió con -una llamada BASIC explícita y redundante a `Map(7, MaskedSprites_MSFS_Page)` -dentro de `InitMaskedSpritesFileSystem()` (comentada como tal — el -análisis de uso del compilador no cuenta las llamadas hechas desde ASM). - -Verificado de nuevo en simulación (mismas 6 direcciones correlativas, -sin `STOP`) — pero siguió fallando en el emulador real. El cambio a -`Map()` era, como señaló el usuario, un no-op ("el Map de la biblioteca -hace exactamente lo mismo que el out"). - -### Causa raíz REAL (tercera vuelta, 2026-07-04): el FSB nunca se inicializa - -La traza del usuario mostraba `FindFirstUnusedBlockInMSFS` recorriendo -el free-space bitmap COMPLETO (bucle FIND-INT, 254 líneas de RRCA/DEC E) -y saliendo por la rama `full` (`SCF/RET` → `JR C` → `LD HL,0`): **todos -los bloques aparecían como "ocupados"**. `__EQ16` (el sospechoso -inicial) funcionaba perfectamente — `regHero0` realmente era 0. - -Causa: **ni nuestra versión ni la original de zx48k limpian jamás el -FSB** (los bytes del bitmap en `start+2..start+1+l`). En el Spectrum no -hace falta: el test de RAM de la ROM deja toda la memoria a cero en el -arranque, así que el bitmap nace "todo libre" gratis. En zx81sd la -página del bloque 7 llega con basura de fábrica → todos los bits a 1 → -"sin bloques libres" → `RegisterSpriteImageInMSFS` devuelve 0 → `STOP`. - -**Por qué el simulador Python dio falso OK dos veces**: su RAM también -nace a ceros, igual que la del Spectrum tras el test de ROM — -exactamente la condición que oculta el bug. Lección de metodología -incorporada al arnés: para validar código que lee memoria no -inicializada, rellenar toda la RAM no cargada con basura (`$FF`) antes -de simular. Con RAM sucia, el binario sin fix reproduce el `STOP` (el -modo de fallo de la traza) y el binario con fix pasa completo (6 -registros correctos, bucle principal alcanzado). - -Fix (3 líneas + comentario en `InitMaskedSpritesFileSystem`): bucle -`FOR j = start+2 TO start+1+l: poke j,0: NEXT` tras calcular el tamaño -del FSB. - -También se verificó por el camino, con `examples/sd81/block7test.bas` -(prefijo BLOCK7), que el mapeador funciona perfectamente: patrones -distintos escritos en páginas 20 y 63 del bloque 7 sobreviven al -intercambio de página (contenido independiente por página). El -mapeador nunca fue el problema. - -### Cuarta vuelta 2026-07-04: sprites como líneas verticales — página residente - -Con el fix del FSB, en el emulador real MSFS ya inicializaba bien -(pantalla: `Init MSFS at 57344`, `Free Blocks = 85`, y los 6 registros -con los valores EXACTOS que predijo la simulación) pero los sprites se -dibujaban como líneas verticales en vez de sus gráficos. - -Causa (descuido de diseño de este override, no del original): -`SaveBackgroundAndDrawSpriteRegisteredInMSFS` — el que dibuja en el -bucle principal y el que fabrica las imágenes desplazadas bajo demanda — -accede a la MSFS **sin envolver con Get/SetBank**. En el diseño original -no lo necesita: en 128K el banco 7 se queda mapeado en `$c000` -(`SetDrawingScreen7`) y en 48K la MSFS está en RAM plana siempre -visible. Nuestra primera versión "liberaba" el bloque 7 de vuelta a la -página 63 al restaurar tras `Init`/`Register...` → el dibujo leía -máscaras y gráficos de la página 63 (basura) → líneas verticales. El -simulador no podía detectarlo: sin mapeador modelado, página 20 y 63 -son la misma RAM plana. - -Fix: la página de MSFS queda **residente** en el bloque 7 desde el -init — `SetBankPreservingINTs` con valor ≠ 7 solo anota el número, no -desmapea (el "liberar a página 63" era invención de este override, -nada lo necesita). Documentado en la cabecera de la librería: si un -programa usa el bloque 7 para su propio banking, debe remapear su -página él mismo y llamar a `SetBankPreservingINTs(7)` antes de volver a -usar MSFS. + `SetDrawingScreen7`/`ToggleDrawingScreen` → safe stubs (real + double-screen buffering isn't covered; dead code in this example + since `memoryPaging=0`, but they no longer touch `$5B5C`/`$7FFD` in + case something calls them directly in the future). + +**Real bug found during the implementation** (not by the user, caught +with the simulation harness itself): my first attempt wrote +`SetBankPreservingINTs`/`GetBankPreservingRegs` in plain BASIC instead +of hand-written ASM. That broke the register contract documented in +the original file itself ("Preserves: D, E, H, L") which +`RegisterSpriteImageInMSFS`'s ASM code and friends take for granted +(for example, so as not to lose `spriteImageAddr`, which arrives in +HL) — a compiled BASIC function uses registers freely inside with no +guarantee of preserving them. Result: all 6 test sprites registered at +the SAME address (`$0C07`) instead of distinct ones. Both primitives +were rewritten in hand-written ASM, with the exact same contract as +the original (only touching A, B, C). + +Verified by simulation: the 6 register addresses (`regHero0`, +`regFoe00`, `regFoe20-23`) come out sequential every 96 bytes exactly +(`$E010, $E070, $E0D0, $E130, $E190, $E1F0`, matching +`$E000+n*96+16`), without triggering `STOP`, and the main loop +(`WaitForNewFrame`) is reached repeatedly with no hang after 1000 +million simulation ticks with no `HALT`/illegal write. + +Known limitation of the Python simulator used in this session: it +doesn't model the memory mapper (`OUT` to `$E7` is a no-op in the +simulation, all RAM is treated as flat) — it can't validate that the +*physical* page swap genuinely works, only that the Z80 logic is +correct assuming it does. The definitive validation is on the +emulator/real hardware. + +### 2026-07-04, second round: still going to HALT on real hardware + +With the fix above already compiled, on the real emulator it still +triggered `__STOP` (same symptom: `RegisterSpriteImageInMSFS` returns +0). The trace confirmed the problem was in `SetBankPreservingINTs`, +which did the `OUT` to port `$E7` by hand instead of calling `Map()` +(mcu.bas): it wrote `A=7` (block, without combining it with the page) +with `B=page`. My initial hypothesis was that the hardware might be in +simple mode (where the data byte must carry page AND block combined, +`(page AND 31)<<3 | block`, and with only `A=7` it would be interpreted +as page=0) — **the user corrected this**: the SD81 loader leaves the +mapper in full mode from the `LOAD *MAP 7,63` line onward, until the +next reset, so that specific explanation doesn't fit (in full mode only +A's 3 low bits matter, the same in both versions). The exact cause was +still unconfirmed at the time of writing this. + +`SetBankPreservingINTs` was changed to call `Map()` (code already +proven in other contexts) instead of repeating the port logic by hand, +preserving D,E,H,L around the call with manual `push`/`pop` (`Map()` +itself preserves nothing). This had a side effect: `Map()` was no +longer referenced from BASIC anywhere (only from hand-written ASM), and +the compiler's dead-code eliminator removed it from the binary → +`Undefined GLOBAL label '._Map'`. Solved with an explicit, redundant +BASIC call to `Map(7, MaskedSprites_MSFS_Page)` inside +`InitMaskedSpritesFileSystem()` (commented as such — the compiler's +usage analysis doesn't count calls made from ASM). + +Verified again in simulation (the same 6 sequential addresses, no +`STOP`) — but it still failed on the real emulator. The switch to +`Map()` was, as the user pointed out, a no-op ("the library's Map does +exactly the same thing as the raw out"). + +### REAL root cause (third round, 2026-07-04): the FSB is never initialized + +The user's trace showed `FindFirstUnusedBlockInMSFS` scanning the +ENTIRE free-space bitmap (the FIND-INT loop, 254 lines of RRCA/DEC E) +and exiting through the `full` branch (`SCF/RET` → `JR C` → `LD HL,0`): +**every block appeared "used"**. `__EQ16` (the initial suspect) worked +perfectly — `regHero0` really was 0. + +Cause: **neither our version nor the original zx48k one ever clears the +FSB** (the bitmap bytes at `start+2..start+1+l`). On the Spectrum +that's not needed: the ROM's RAM test leaves all memory zeroed at +boot, so the bitmap is born "all free" for free. On zx81sd, block 7's +page arrives with factory garbage → every bit set to 1 → "no free +blocks" → `RegisterSpriteImageInMSFS` returns 0 → `STOP`. + +**Why the Python simulator gave a false OK twice**: its RAM also starts +at zero, just like a Spectrum's after the ROM test — exactly the +condition that hides the bug. Methodology lesson added to the harness: +to validate code that reads uninitialized memory, fill all unloaded RAM +with garbage (`$FF`) before simulating. With dirty RAM, the unfixed +binary reproduces the `STOP` (the trace's failure mode) and the fixed +binary passes completely (6 correct registers, main loop reached). + +Fix (3 lines + a comment in `InitMaskedSpritesFileSystem`): a +`FOR j = start+2 TO start+1+l: poke j,0: NEXT` loop after computing the +FSB's size. + +Along the way, it was also confirmed with +`examples/sd81/block7test.bas` (BLOCK7 prefix) that the mapper works +perfectly: different patterns written to pages 20 and 63 of block 7 +survive the page switch (independent content per page). The mapper was +never the problem. + +### 2026-07-04, fourth round: sprites as vertical lines — resident page + +With the FSB fix, on the real emulator MSFS now initialized correctly +(screen: `Init MSFS at 57344`, `Free Blocks = 85`, and all 6 registers +with the EXACT values predicted by the simulation) but the sprites were +being drawn as vertical lines instead of their actual graphics. + +Cause (a design oversight of this override, not the original): +`SaveBackgroundAndDrawSpriteRegisteredInMSFS` — the function that draws +in the main loop and manufactures shifted images on demand — accesses +MSFS **without wrapping it in Get/SetBank**. In the original design it +doesn't need to: on 128K, bank 7 stays mapped at `$c000` +(`SetDrawingScreen7`) and on 48K, MSFS is always visible in flat RAM. +Our first version "released" block 7 back to page 63 when restoring +after `Init`/`Register...` → drawing read masks and graphics from page +63 (garbage) → vertical lines. The simulator couldn't detect this: +with no mapper modeled, page 20 and 63 are the same flat RAM. + +Fix: MSFS's page stays **resident** in block 7 from init onward — +`SetBankPreservingINTs` with a value ≠ 7 only records the number, it +doesn't unmap (the "release back to page 63" was an invention of this +override, nothing needs it). Documented in the library's header: if a +program uses block 7 for its own banking, it must remap its own page +and call `SetBankPreservingINTs(7)` before using MSFS again. diff --git a/src/arch/zx81sd/doc/PRECAUCIONES.md b/src/arch/zx81sd/doc/PRECAUCIONES.md deleted file mode 100644 index 8c22fb451..000000000 --- a/src/arch/zx81sd/doc/PRECAUCIONES.md +++ /dev/null @@ -1,220 +0,0 @@ -# Precauciones al escribir o portar software para zx81sd - -zx81sd hace que ZX BASIC genere binarios que "parecen" un Spectrum (la -interfaz SD81 Booster emula su pantalla, y buena parte de la stdlib -compartida asume convenciones del Spectrum), pero **no hay ROM del -Spectrum en ningún sitio**: no hay `RST $28` de la ROM, no hay rutinas -en direcciones fijas, no hay sysvars del Spectrum en `$5C00+`. Casi -todos los bugs de este port han venido de código (de examples/ oficiales -o de la stdlib compartida) que asume silenciosamente alguna de estas -cosas. Antes de portar algo, repasa esta lista. - -## 1. Nunca hay ROM: cuidado con direcciones absolutas y sysvars - -Cualquier `POKE`/`PEEK`/`CALL` a una dirección numérica fija -(23675, 23658, $22AC, $0DFE...) casi seguro que es una sysvar o rutina -de la **ROM del Spectrum**, que en zx81sd no existe: esa dirección cae -en RAM libre, o peor, en pleno código compilado del programa — -ejecutarla o interpretarla como dato produce corrupción silenciosa, -gráficos erróneos, o un `HALT`/reinicio salvaje muy difícil de -relacionar con la causa (varios bugs de este port tardaron sesiones -enteras en diagnosticarse por esto). - -- **Sysvars del Spectrum → sysvars de zx81sd**: la tabla de - equivalencias está en - [`../../../lib/arch/zx81sd/runtime/sysvars.asm`](../../../lib/arch/zx81sd/runtime/sysvars.asm) - (todas viven en `$8000+`, no en `$5C00+`). Ejemplos ya resueltos: - `UDG` (23675 → `$8002`), `COORDS` (23677/23678 → `$8004`/`$8005`). - Ver [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md) para el detalle línea a - línea de cada caso ya encontrado. -- **Rutinas de ROM llamadas directamente** (`call $22AC` = PIXEL-ADD, - `call $0DFE` = CL-SC-ALL/scroll, `RST $28` = calculador FP...): si el - fuente o una librería compartida hace esto, hace falta un override en - `src/lib/arch/zx81sd/` que sustituya la llamada por la rutina propia - con el **mismo contrato de registros** que la de la ROM (ver más - abajo). Ejemplo resuelto: `stdlib/scroll.bas`. -- **`grep` preventivo**: al portar un fichero de `zx48k/` a `zx81sd/`, - buscar `EQU 0[0-9A-F]` / `call 0x` / literales de 4-5 dígitos - sospechosos antes de darlo por bueno, no solo cuando algo falla. - -## 2. Contratos de registros de las rutinas ASM: son sagrados - -Varias rutinas del runtime tienen contratos de preservación de -registros explícitos y no negociables porque el código que las llama -(heredado de zx48k, no tocable) depende de ellos al pie de la letra. -Ejemplos: - -- `PIXEL_ADDR` (`runtime/pixel_addr.asm`): A=191, B=Y, C=X → HL=offset, - A=X AND 7; **destruye B, preserva D y E**. `draw.asm` guarda la - coordenada Bresenham en D alrededor de la llamada confiando en esto - literalmente — romperlo (como pasó una vez con un intento que usaba D - de scratch) corrompe cualquier línea con componente vertical sin - tocar para nada las horizontales, lo que despista mucho a la hora de - diagnosticar. -- `GetBankPreservingRegs`/`SetBankPreservingINTs` (MSFS, - `cb/maskedsprites.bas`): contrato documentado "preserva D,E,H,L". - Escribir el reemplazo en BASIC plano en vez de ASM a mano rompe esto - sin ningún aviso del compilador (el código BASIC generado usa - registros libremente por dentro) — un bug real de esta clase hizo que - 6 sprites de prueba se registraran todos en la misma dirección - incorrecta. **Nota**: el port de MSFS/`maskedsprites.bas` sigue en - proceso (aún no funciona del todo bien) — este ejemplo concreto sirve - para ilustrar el tipo de bug, no como confirmación de que la librería - ya esté terminada. - -**Regla práctica**: si vas a sustituir una rutina ASM que tiene un -contrato de registros documentado (o que se puede inferir mirando quién -la llama y qué asume), reimplaza en ASM a mano preservando exactamente -ese contrato. Una función BASIC (`SUB`/`FUNCTION`), por sencilla que -parezca, NO es un reemplazo válido salvo que el contrato sea "ninguno". - -## 3. El teclado es el del ZX81, no el del Spectrum - -El SD81 Booster no tiene teclado Spectrum: reescanea el teclado físico -de 40 teclas del ZX81 (`runtime/io/keyboard/keyscan.asm`). Diferencias -que importan al portar/escribir código: - -- Pulsación directa de una letra: minúscula. Con `SHIFT+letra`: - MAYÚSCULA de esa letra — exactamente igual que en un Spectrum real, a - diferencia del ZX81 original (donde `SHIFT+letra` daba un símbolo, no - una mayúscula). Esta redefinición es una decisión de diseño de este - port, ver [MAP.md](MAP.md) sección "Esquema de teclado nuevo". En - consecuencia, **sí se puede escanear/comparar `INKEY$` contra - mayúsculas de letra** (`IF INKEY$="S"`, pensado para jugarse con - `SHIFT` sostenido al estilo Spectrum) sin ninguna limitación de - hardware: basta con pulsar `SHIFT+S`. -- `SHIFT+"2"` alterna un CAPS LOCK persistente. -- `"."` es una tecla normal (`.` sin shift, `,` con shift). Los símbolos - del ZX81 original asociados a cada tecla (`:` en Z, `)` en O, etc.) se - alcanzan con la secuencia `"." + tecla` **solo desde `INPUT()`** (tecla - muerta gestionada en `stdlib/input.bas`), no desde `INKEY$` a pelo — - no hay forma de "sostener ambas a la vez" con fiabilidad en este - teclado, así que la composición se hace pulsando `.` primero y la - segunda tecla después. Pulsar `.` **dos veces seguidas** confirma el - primer punto como literal y descarta cualquier combo: la tecla que - venga después de ese segundo punto se lee como una pulsación nueva, - sin combinar con nada (permite escribir cualquier tecla justo después - de un punto sin arriesgarse a formar un símbolo por accidente). - -## 4. No hay interrupciones: nunca esperes un `HALT`/`EI` para sincronizar - -El runtime de zx81sd corre permanentemente con interrupciones -deshabilitadas (`DI`); el vector `$0038` es solo una trampa `DI;HALT`, -no un manejador de interrupción real. Cualquier código (típicamente -código ASM inline de un ejemplo, no de la stdlib) que haga `EI` seguido -de `HALT` esperando el pulso de 50Hz de la ROM del Spectrum **se cuelga -para siempre** — no hay nada que lo despierte. - -- **Sustituto**: `VSYNC_TICK` (namespace `core`, en - `runtime/vsync.asm`) sondea por puerto el contador de pulsos VSYNC - real del hardware SD81 Booster. Ya lo usa `PAUSE` internamente. -- Al llamarlo desde un bloque `ASM ... END ASM` que no esté ya dentro de - `push namespace core`, hay que usar el prefijo completo: - `call .core.VSYNC_TICK` (si se omite el prefijo, el compilador da - `Undefined GLOBAL label '.VSYNC_TICK'` — error ya visto más de una - vez en este port). -- Un contador que antes se incrementaba solo por la interrupción en - segundo plano (`FRAMES`/23672 en el Spectrum) hay que actualizarlo a - mano llamando a `VSYNC_TICK` explícitamente en cada vuelta del bucle - de espera, no solo una vez al principio. - -## 5. Namespaces y mangling de etiquetas ASM - -Un `DIM X` o `SUB`/`FUNCTION X` de BASIC se traduce a la etiqueta ASM -`_X` (un solo guion bajo), **salvo** que el fichero envuelva su código -en `push namespace core ... pop namespace`, en cuyo caso hay que -referenciarla desde fuera como `.core._X` (variables) o `.core.X` -(funciones/rutinas). Confundir esto en cualquier dirección produce -`Undefined GLOBAL label`. Si un fichero de este port no usa namespacing -en ningún otro sitio, no hace falta envolver un bloque `ASM` nuevo en -`push namespace core` solo porque otro fichero (como `vsync.asm`) sí lo -use — basta con prefijar la referencia puntual. - -## 6. El eliminador de código muerto no ve las llamadas desde ASM a mano - -El análisis de "¿esto se usa?" del compilador solo cuenta llamadas -hechas con sintaxis BASIC (`Foo(x)`). Una `SUB`/`FUNCTION`/variable -BASIC referenciada **solo** desde un bloque `ASM ... END ASM` (p. ej. -`call _Foo`) puede ser eliminada como código muerto, dando -`Undefined GLOBAL label '._Foo'` al enlazar — el símbolo nunca llegó a -existir en el binario final. Dos salidas: - -- Si es un dato puro (un byte de estado, por ejemplo), declararlo como - ASM puro (`ASM \n _Label: \n DEFB 0 \n END ASM` a nivel de fichero), - no como `DIM`. -- Si es una función que de verdad hace falta que sea BASIC (porque - llama a otras cosas de la stdlib), añadir una llamada BASIC real - (aunque sea redundante/no estrictamente necesaria en ese punto) en - algún sitio alcanzable del código, para que el análisis de uso la - cuente. - -## 7. Metodología de depuración sin hardware - -Para diagnosticar sin gastar ciclos de prueba-error en el emulador o el -hardware real, este port usa simulación directa del binario con el -paquete `z80` de Python. Dos lecciones ya aprendidas por las malas -(documentadas con más detalle en [MAP.md](MAP.md)): - -- Comprobar el PC periódicamente cada N ticks gruesos puede dar falsos - "atascado" si justo cae siempre en el mismo punto de un bucle; usar - breakpoints reales (comparar `m.pc` contra la dirección exacta, - sacada del `.map`) o chunks de tick más finos. -- La RAM del simulador nace a **ceros**, igual que la del Spectrum tras - el test de RAM de su ROM. Esto oculta bugs de memoria no inicializada - (dio dos falsos "OK" seguidos en el bug del free-space-bitmap de - MSFS). Para validar código que lee memoria que no inicializa él - mismo, rellenar toda la RAM no cargada con `0xFF` antes de cargar el - binario, para reproducir las condiciones reales de hardware/tarjeta. -- El simulador **no modela el mapeador de memoria** (`OUT` al puerto - `$E7` es un no-op): puede validar que la lógica Z80 es autoconsistente, - pero no que el intercambio de página físico funcione de verdad — eso - solo se confirma en el emulador (EightyOne) o en hardware real. -- Si un binario falla en EightyOne pero la simulación Python lo ejecuta - limpio, sospecha primero del emulador (ver el bug de traps de cinta ya - encontrado y corregido en `Eightyone2/src/ZX81/rompatch.cpp`, en el - repositorio del emulador, no en este) antes que del runtime — en - hardware real esos traps no existen. - -## 8. Nunca hagas `#include once ` desde una librería propia - -Si necesitas los sysvars propios de zx81sd (`CHARS`/`UDG`/`ATTR_P`/ -`SCREEN_ADDR`/`SCREEN_ATTR_ADDR`...) desde un fichero `stdlib/*.bas` -nuevo, **no lo incluyas tú mismo con `#include once `** — -solo referencia los símbolos con el prefijo `.core.` (ver punto 5) y -confía en que el resto del runtime ya lo trajo. - -`sysvars.asm` arrastra tras de sí `bootstrap.asm` → `charset.asm`, y -este último hace `INCBIN "specfont.bin"` (el font completo, bytes -binarios, no texto ensamblador). Si tu fichero resulta ser el -**primero** en incluir `sysvars.asm` en todo el programa compilado -(fácil que pase: un `#include ` al principio del fuente -del usuario se procesa antes que cualquier `CLS`/`PRINT` textualmente -posterior), ese `INCBIN` se emite **justo en el punto del fichero fuente -donde pusiste el `#include`**: - -- Puesto a nivel BASIC (fuera de un bloque `ASM ... END ASM`): el lexer - de BASIC intenta tokenizar esos bytes binarios como si fueran texto - fuente → `error: illegal preprocessor character` en líneas que no - tienen nada que ver con el problema real (mal atribuidas al inicio - del fichero). -- Puesto dentro de un bloque `ASM` (p. ej. al principio del cuerpo de - una función): el binario del font se emite literalmente en medio del - código compilado de esa función — compila sin error, pero la CPU - "ejecuta" esos bytes de font como si fueran instrucciones en cuanto - el flujo de control cae ahí, produciendo un `HALT` o comportamiento - errático en una dirección que no tiene relación aparente con el bug - (encontrado al portar `print42.bas`/`print64.bas`, ver - [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md)). - -Cualquier programa real que use tu librería casi seguro que también usa -`CLS`/`PRINT` en algún punto, y esas rutinas ya requieren `sysvars.asm` -— así que omitir el `#include` en tu fichero es seguro en la práctica, -no una chapuza. - -## Ver también - -- [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md) — catálogo de cambios de fuente - ya necesarios en ejemplos oficiales, con el patrón general a buscar - en cualquier ejemplo nuevo. -- [MAP.md](MAP.md) — bitácora técnica completa, bug a bug, con las - trazas de investigación. diff --git a/src/arch/zx81sd/doc/PRECAUTIONS.md b/src/arch/zx81sd/doc/PRECAUTIONS.md new file mode 100644 index 000000000..3ab31ace0 --- /dev/null +++ b/src/arch/zx81sd/doc/PRECAUTIONS.md @@ -0,0 +1,216 @@ +# Precautions when writing or porting software for zx81sd + +zx81sd makes ZX BASIC generate binaries that "look like" a Spectrum +(the SD81 Booster interface emulates its screen, and much of the +shared stdlib assumes Spectrum conventions), but **there is no +Spectrum ROM anywhere**: no ROM `RST $28`, no routines at fixed +addresses, no Spectrum sysvars at `$5C00+`. Almost every bug in this +port has come from code (official `examples/` or the shared stdlib) +that silently assumes one of these things. Check this list before +porting anything. + +## 1. There's never a ROM: watch out for absolute addresses and sysvars + +Any `POKE`/`PEEK`/`CALL` to a fixed numeric address (23675, 23658, +$22AC, $0DFE...) is almost certainly a **Spectrum ROM** sysvar or +routine, which doesn't exist in zx81sd: that address lands in free RAM, +or worse, in the middle of the program's own compiled code — executing +or interpreting it as data causes silent corruption, wrong graphics, or +a runaway `HALT`/reset that's very hard to trace back to its cause +(several bugs in this port took whole sessions to diagnose because of +this). + +- **Spectrum sysvars → zx81sd sysvars**: the equivalence table is in + [`../../../lib/arch/zx81sd/runtime/sysvars.asm`](../../../lib/arch/zx81sd/runtime/sysvars.asm) + (all live at `$8000+`, not `$5C00+`). Examples already solved: + `UDG` (23675 → `$8002`), `COORDS` (23677/23678 → `$8004`/`$8005`). + See [BASIC_CHANGES.md](BASIC_CHANGES.md) for the line-by-line detail + of every case found so far. +- **ROM routines called directly** (`call $22AC` = PIXEL-ADD, + `call $0DFE` = CL-SC-ALL/scroll, `RST $28` = FP calculator...): if the + source or a shared library does this, it needs an override in + `src/lib/arch/zx81sd/` that replaces the call with our own routine, + keeping the **same register contract** as the ROM one (see below). + Example already solved: `stdlib/scroll.bas`. +- **Preventive `grep`**: when porting a file from `zx48k/` to + `zx81sd/`, search for `EQU 0[0-9A-F]` / `call 0x` / suspicious + 4-5-digit literals before signing off on it, not only once something + actually fails. + +## 2. ASM routine register contracts are sacred + +Several runtime routines have explicit, non-negotiable register +preservation contracts, because the code that calls them (inherited +from zx48k, not something we can touch) depends on them to the letter. +Examples: + +- `PIXEL_ADDR` (`runtime/pixel_addr.asm`): A=191, B=Y, C=X → HL=offset, + A=X AND 7; **destroys B, preserves D and E**. `draw.asm` stores the + Bresenham coordinate in D around the call, trusting this literally — + breaking it (as happened once with an attempt that used D as + scratch) corrupts every line with a vertical component while leaving + horizontal ones untouched, which is very misleading to diagnose. +- `GetBankPreservingRegs`/`SetBankPreservingINTs` (MSFS, + `cb/maskedsprites.bas`): documented contract "preserves D,E,H,L". + Writing the replacement in plain BASIC instead of hand-written ASM + breaks this with no warning from the compiler (compiled BASIC code + uses registers freely internally) — a real bug of this kind made all + 6 test sprites register at the same wrong address. **Note**: the + MSFS/`maskedsprites.bas` port is still in progress (still not fully + working) — this specific example is here to illustrate the class of + bug, not to confirm the library is finished. + +**Practical rule**: if you're going to replace an ASM routine that has +a documented register contract (or one that can be inferred from +looking at its callers and what they assume), reimplement it in +hand-written ASM preserving that exact contract. A BASIC function +(`SUB`/`FUNCTION`), however simple it looks, is NOT a valid replacement +unless the contract is "none". + +## 3. The keyboard is the ZX81's, not the Spectrum's + +The SD81 Booster doesn't have a Spectrum keyboard: it rescans the +ZX81's own physical 40-key keyboard (`runtime/io/keyboard/keyscan.asm`). +Differences that matter when porting/writing code: + +- Direct key press: lowercase. `SHIFT+letter`: UPPERCASE of that letter + — exactly like a real Spectrum, unlike the original ZX81 (where + `SHIFT+letter` produced a symbol, not an uppercase letter). This + redefinition is a design decision of this port, see [MAP.md](MAP.md) + section "New keyboard scheme". As a result, **you can indeed + scan/compare `INKEY$` against uppercase letters** + (`IF INKEY$="S"`, meant to be played holding `SHIFT` Spectrum-style) + with no hardware limitation: just press `SHIFT+S`. +- `SHIFT+"2"` toggles a persistent CAPS LOCK. +- `"."` is a normal key (`.` unshifted, `,` shifted). The original + ZX81 symbols associated with each key (`:` on Z, `)` on O, etc.) are + reached with the `"." + key` sequence **only from `INPUT()`** (a + dead-key handled in `stdlib/input.bas`), not from raw `INKEY$` — there + is no reliable way to "hold both at once" on this keyboard, so the + composition is done by pressing `.` first and the second key after. + Pressing `.` **twice in a row** confirms the first dot as a literal + and cancels any pending combo: the key that comes after that second + dot is read as a fresh keypress, with no combination (lets you type + any key right after a dot without risking forming a symbol by + accident). + +## 4. There are no interrupts: never wait on `HALT`/`EI` to synchronize + +The zx81sd runtime always runs with interrupts disabled (`DI`); the +`$0038` vector is only a `DI;HALT` trap, not a real interrupt handler. +Any code (typically inline ASM in an example, not the stdlib) that +does `EI` followed by `HALT` waiting for the Spectrum ROM's 50Hz pulse +**hangs forever** — nothing ever wakes it up. + +- **Replacement**: `VSYNC_TICK` (namespace `core`, in + `runtime/vsync.asm`) polls the SD81 Booster hardware's real VSYNC + pulse counter through a port. `PAUSE` already uses it internally. +- When calling it from an `ASM ... END ASM` block that isn't already + inside `push namespace core`, you need the full prefix: + `call .core.VSYNC_TICK` (omitting the prefix gives + `Undefined GLOBAL label '.VSYNC_TICK'` — an error already seen more + than once in this port). +- A counter that used to be incremented only by the background + interrupt (`FRAMES`/23672 on the Spectrum) needs to be updated by + hand, calling `VSYNC_TICK` explicitly on every loop iteration that + still needs to wait, not just once at the start. + +## 5. ASM label namespaces and mangling + +A BASIC `DIM X` or `SUB`/`FUNCTION X` translates to the ASM label `_X` +(a single leading underscore), **unless** the file wraps its code in +`push namespace core ... pop namespace`, in which case you must +reference it from outside as `.core._X` (variables) or `.core.X` +(functions/routines). Getting this wrong in either direction produces +`Undefined GLOBAL label`. If a file in this port doesn't use +namespacing anywhere else, there's no need to wrap a new `ASM` block in +`push namespace core` just because another file (like `vsync.asm`) +does — just prefix the specific reference. + +## 6. The dead-code eliminator can't see calls made from hand-written ASM + +The compiler's "is this used?" analysis only counts calls made with +BASIC call syntax (`Foo(x)`). A BASIC `SUB`/`FUNCTION`/variable +referenced **only** from an `ASM ... END ASM` block (e.g. `call _Foo`) +can be eliminated as dead code, producing `Undefined GLOBAL label +'._Foo'` at link time — the symbol never made it into the final +binary. Two ways out: + +- If it's pure data (a state byte, for example), declare it as raw ASM + (`ASM \n _Label: \n DEFB 0 \n END ASM` at file scope), not as `DIM`. +- If it's a function that genuinely needs to be BASIC (because it + calls other stdlib things), add one real BASIC-level call (even if + redundant/not strictly needed at that point) somewhere reachable in + the code, so the usage analysis counts it. + +## 7. Debugging methodology without hardware + +To diagnose things without burning trial-and-error cycles on the +emulator or real hardware, this port uses direct simulation of the +binary with Python's `z80` package. Two lessons already learned the +hard way (documented in more detail in [MAP.md](MAP.md)): + +- Checking the PC periodically every N coarse ticks can give a false + "stuck" reading if it just happens to always land on the same point + of a loop; use real breakpoints (compare `m.pc` against the exact + address, taken from the `.map`) or finer tick chunks. +- The simulator's RAM starts at **zero**, just like a Spectrum's after + its ROM's RAM test. This hides uninitialized-memory bugs (it gave two + false "OK" results in a row in the MSFS free-space-bitmap bug). To + validate code that reads memory it doesn't initialize itself, fill + all unloaded RAM with `0xFF` before loading the binary, to reproduce + real hardware/card conditions. +- The simulator **doesn't model the memory mapper** (`OUT` to port + `$E7` is a no-op): it can validate that the Z80 logic is + self-consistent, but not that the actual physical page-swapping + works — that's only confirmed on the emulator (EightyOne) or real + hardware. +- If a binary fails on EightyOne but the Python simulation runs it + clean, suspect the emulator first (see the tape-trap bug already + found and fixed in `Eightyone2/src/ZX81/rompatch.cpp`, in the + emulator's own repository, not this one) before the runtime — those + traps don't exist on real hardware. + +## 8. Never do `#include once ` from your own library + +If you need zx81sd's own sysvars (`CHARS`/`UDG`/`ATTR_P`/ +`SCREEN_ADDR`/`SCREEN_ATTR_ADDR`...) from a new `stdlib/*.bas` file, +**don't include it yourself with `#include once `** — just +reference the symbols with the `.core.` prefix (see point 5) and trust +that the rest of the runtime already brought it in. + +`sysvars.asm` drags in `bootstrap.asm` → `charset.asm`, and the latter +does `INCBIN "specfont.bin"` (the complete font, binary bytes, not +assembly text). If your file turns out to be the **first** one to +include `sysvars.asm` in the whole compiled program (easy to happen: a +`#include ` at the top of the user's source is +processed before any `CLS`/`PRINT` that comes later in the text), that +`INCBIN` gets emitted **right at the point in the source file where you +put the `#include`**: + +- Placed at BASIC level (outside an `ASM ... END ASM` block): the + BASIC lexer tries to tokenize those binary bytes as if they were + source text → `error: illegal preprocessor character` on lines that + have nothing to do with the real problem (misattributed to the start + of the file). +- Placed inside an `ASM` block (e.g. at the start of a function body): + the font binary gets emitted literally in the middle of that + function's compiled code — it compiles without error, but the CPU + "executes" those font bytes as if they were instructions the moment + control flow falls there, producing a `HALT` or erratic behavior at + an address that appears completely unrelated to the bug (found while + porting `print42.bas`/`print64.bas`, see + [BASIC_CHANGES.md](BASIC_CHANGES.md)). + +Any real program using your library will almost certainly also use +`CLS`/`PRINT` somewhere, and those routines already require +`sysvars.asm` — so leaving out the `#include` in your file is safe in +practice, not a hack. + +## See also + +- [BASIC_CHANGES.md](BASIC_CHANGES.md) — catalog of source changes + already needed in official examples, with the general pattern to look + for in any new example. +- [MAP.md](MAP.md) — full technical log, bug by bug, with the + investigation traces. diff --git a/src/arch/zx81sd/doc/USAGE.md b/src/arch/zx81sd/doc/USAGE.md new file mode 100644 index 000000000..43571baf8 --- /dev/null +++ b/src/arch/zx81sd/doc/USAGE.md @@ -0,0 +1,88 @@ +# Usage: compiling, packaging and loading a program on zx81sd + +## 1. Compile + +From the root of this repository: + +``` +python -m src.zxbc.zxbc --arch zx81sd -o -M +``` + +- `--arch zx81sd` selects this architecture's backend and overrides + (see [../README.md](../README.md) — the port's golden rule). +- `-M ` is optional but strongly recommended: it generates + the symbol map (address of every ASM/BASIC label), essential for + debugging with an emulator or simulating the binary (see + [MAP.md](MAP.md) for examples of Python simulation harnesses). +- Examples transcribed from classic Sinclair BASIC (1-based + arrays/strings) usually also need `--string-base 1 --array-base 1` + (see [BASIC_CHANGES.md](BASIC_CHANGES.md), `comecoquitos.bas` example). + +There's also `python zxbc.py --arch zx81sd -f bin -o ` +(the traditional entry-point script); both forms are equivalent. + +## 2. Package for the ZX81 + +The binary produced by the compiler is a flat image of up to 48KB +(blocks 0-5 of the SD81 mapper). The ZX81 can only load, in one go, +whatever fits in its own visible RAM, so that binary has to be split +into 8KB pages and a BASIC loader generated that feeds them one by one +into the SD81 Booster card, remapping the memory mapper between pages. + +That's what [`../tools/split_sd81.py`](../tools/split_sd81.py) does: + +``` +python src/arch/zx81sd/tools/split_sd81.py [PREFIX] +``` + +- `PREFIX` (optional) is the base name for the output files, in the + ZX81 charset (letters, digits, no underscore). If omitted, it's + derived from the input `.bin` file name. +- It generates: + - `P8.BIN`, `P9.BIN`, ... — one per 8KB of the binary + (SD81 page 8 = block 0, page 9 = block 1, etc.) + - `_loader.txt` — the loader's BASIC listing in plain text + (for reading/debugging). + - `.P` — the same loader, already tokenized, ready to load + and run on the ZX81 from the SD card. + +The generated loader uses `LOAD THEN CLEAR`, `LOAD *MAP` and +`LOAD FAST ... CODE`, extensions provided by the SD81 Booster firmware +(they don't exist in the original ZX81 ROM). It does the following, in +order: + +1. Reserves memory (`CLEAR`) and loads `BOOT1.BIN` (the stage 1 loader, + fixed for every program — source in + [`../tools/boot1.asm`](../tools/boot1.asm), already-assembled binary + in [`../tools/boot1.bin`](../tools/boot1.bin)). +2. For each page of the binary: maps block 7 to that physical page + (`LOAD *MAP 7,`) and dumps the data there (`LOAD FAST ... CODE + 57344`, block 7's window at `$E000`). +3. When done, leaves the mapper in "full page" mode (`LOAD *MAP + 7,63`) — from this point on the mapper doesn't go back to simple + mode until the next reset — and jumps to `BOOT1.BIN` (`RAND USR + 24576`), which does the final mapping of blocks 0-5 and starts the + program. + +## 3. Copy to the SD card + +Copy to the SD card, alongside the rest of your collection: + +- `BOOT1.BIN` (only once, it's the same for every program) +- All the `P.BIN` files for the program +- `.P` + +Then on the ZX81 (or in EightyOne pointing at the SD image): load and +run it with `LOAD FAST ""` — no need to select anything +afterwards, it runs straight from there. + +## 4. Debugging without hardware: simulation with Python + +To diagnose hangs, HALTs or incorrect results without having to test on +the emulator or real hardware every time, this port's development has +used Python's `z80` package (`pip install z80`) to simulate the flat +binary directly. The full methodology (including one important trap: +the simulator's RAM starts at zero, which can hide uninitialized-memory +bugs that do show up on real hardware) is documented in +[MAP.md](MAP.md) — the "Heap at $8100 + EightyOne tape traps" section +and the methodology notes on the MSFS/maskedsprites bug. diff --git a/src/arch/zx81sd/doc/USO.md b/src/arch/zx81sd/doc/USO.md deleted file mode 100644 index 6ed141ddc..000000000 --- a/src/arch/zx81sd/doc/USO.md +++ /dev/null @@ -1,87 +0,0 @@ -# Uso: compilar, empaquetar y cargar un programa en zx81sd - -## 1. Compilar - -Desde la raíz de este repositorio: - -``` -python -m src.zxbc.zxbc --arch zx81sd -o -M -``` - -- `--arch zx81sd` selecciona el backend y los overrides de esta - arquitectura (ver [../README.md](../README.md) — regla de oro del port). -- `-M ` es opcional pero muy recomendable: genera el mapa de - símbolos (dirección de cada label ASM/BASIC), imprescindible para - depurar con un emulador o simular el binario (ver [MAP.md](MAP.md) - para ejemplos de arneses de simulación en Python). -- Ejemplos transcritos de Sinclair BASIC clásico (arrays/strings - 1-based) suelen necesitar además `--string-base 1 --array-base 1` - (ver [CAMBIOS_BASIC.md](CAMBIOS_BASIC.md), ejemplo `comecoquitos.bas`). - -También existe `python zxbc.py --arch zx81sd -f bin -o ` -(script de entrada tradicional); ambas formas son equivalentes. - -## 2. Empaquetar para el ZX81 - -El binario que produce el compilador es una imagen plana de hasta 48KB -(bloques 0-5 del mapeador SD81). El ZX81 solo puede cargar de golpe lo -que quepa en su propia RAM visible, así que hay que partir ese binario -en páginas de 8KB y generar un cargador BASIC que las vaya metiendo en -la tarjeta SD81 Booster una a una, remapeando el mapeador de memoria -entre página y página. - -Esto lo hace [`../tools/split_sd81.py`](../tools/split_sd81.py): - -``` -python src/arch/zx81sd/tools/split_sd81.py [PREFIJO] -``` - -- `PREFIJO` (opcional) es el nombre base de los ficheros de salida, en - el charset del ZX81 (letras, dígitos, sin guion bajo). Si se omite, - se deriva del nombre del `.bin` de entrada. -- Genera: - - `P8.BIN`, `P9.BIN`, ... — una por cada 8KB del - binario (página SD81 8 = bloque 0, 9 = bloque 1, etc.) - - `_loader.txt` — el listado BASIC del cargador en texto - plano (para leer/depurar). - - `.P` — el mismo cargador, ya tokenizado, listo para - cargar y ejecutar en el ZX81 (`LOAD ""` desde la SD). - -El cargador generado usa `LOAD THEN CLEAR`, `LOAD *MAP` y -`LOAD FAST ... CODE`, extensiones del firmware del SD81 Booster (no -existen en la ROM original del ZX81). Hace lo siguiente, en orden: - -1. Reserva memoria (`CLEAR`) y carga `BOOT1.BIN` (el stage 1, fijo para - todos los programas — fuente en [`../tools/boot1.asm`](../tools/boot1.asm), - binario ya ensamblado en [`../tools/boot1.bin`](../tools/boot1.bin)). -2. Por cada página del binario: mapea el bloque 7 a esa página física - (`LOAD *MAP 7,`) y vuelca los datos ahí (`LOAD FAST ... CODE - 57344`, la ventana del bloque 7 en `$E000`). -3. Al terminar, deja el mapeador en modo "página completa" (`LOAD *MAP - 7,63`) — a partir de aquí el mapeador ya no vuelve a modo simple - hasta el siguiente reset — y salta a `BOOT1.BIN` (`RAND USR 24576`), - que hace el mapeo definitivo de los bloques 0-5 y arranca el - programa. - -## 3. Copiar a la tarjeta SD - -Copia a la SD, junto al resto de tu colección: - -- `BOOT1.BIN` (una sola vez, es el mismo para todos los programas) -- Todas las `P.BIN` del programa -- `.P` - -Y en el ZX81 (o en EightyOne apuntando a la imagen de la SD): `LOAD ""` -y selecciona ``. - -## 4. Depurar sin hardware: simulación con Python - -Para diagnosticar cuelgues, HALTs o resultados incorrectos sin tener -que probar en el emulador o el hardware real cada vez, el desarrollo de -este port ha usado el paquete `z80` de Python (`pip install z80`) para -simular el binario plano directamente. La metodología completa -(incluida una trampa importante: la RAM del simulador nace a ceros, lo -que puede ocultar bugs de memoria no inicializada que sí aparecen en -hardware real) está documentada en [MAP.md](MAP.md) — sección "Heap en -$8100 + traps de cinta de EightyOne" y las notas de metodología del bug -de MSFS/maskedsprites. diff --git a/src/arch/zx81sd/tools/boot1.asm b/src/arch/zx81sd/tools/boot1.asm index 16217c4de..7eaa5830f 100644 --- a/src/arch/zx81sd/tools/boot1.asm +++ b/src/arch/zx81sd/tools/boot1.asm @@ -1,33 +1,33 @@ ; =========================================================================== ; bootstrap_stage1.asm — Stage 1 Bootstrap ZX81 + SD81 Booster ; -; Reside en $6000 (bloque 3). El cargador BASIC lo envía allí antes de -; nada porque es una zona neutral: BASIC del ZX81 no la necesita, y aún -; no hemos remapeado ningún bloque. +; Resides at $6000 (block 3). The BASIC loader sends it there before +; anything else because it's a neutral area: the ZX81's BASIC doesn't +; need it, and no block has been remapped yet. ; -; Secuencia de inicio (ver ZX81-SD81-Adaptation-Plan.md): -; 1. DI — deshabilitar interrupciones (el ZX81 FAST mode ya lo hace, pero -; lo repetimos por seguridad antes de tocar la paginación) -; 2. Activar modo Superfast HiRes Spectrum vía FPGA (POKE 2045 = $07FD) -; 3. Desactivar IO mapeado en memoria (POKE 2056 = $0808) -; 4. Mapear bloque 0 → página 8 (OUT ($E7), page=8, block=0) -; El stage 2 ($0100-$0FFF en página 8) está ahora listo para ejecutar. -; 5. JP $0100 — salta al stage 2 en la RAM recién mapeada +; Startup sequence (see ZX81-SD81-Adaptation-Plan.md): +; 1. DI — disable interrupts (ZX81 FAST mode already does this, but +; it's repeated as a precaution before touching paging) +; 2. Activate Superfast HiRes Spectrum mode via the FPGA (POKE 2045 = $07FD) +; 3. Disable memory-mapped IO (POKE 2056 = $0808) +; 4. Map block 0 -> page 8 (OUT ($E7), page=8, block=0) +; Stage 2 ($0100-$0FFF on page 8) is now ready to run. +; 5. JP $0100 — jumps to stage 2 in the freshly mapped RAM ; -; Notas: -; - HFILE=$C000 es el valor por defecto de la FPGA al activar modo 172. -; Si tu hardware requiere configurarlo explícitamente, descomenta el -; bloque HFILE al final. -; - El stage 1 NO inicializa SP; el stage 2 lo hace (ld sp, $7FFF). -; - Bloques 1-5 se mapean en el stage 2 (ya ejecutando desde página 8). +; Notes: +; - HFILE=$C000 is the FPGA's default value when activating mode 172. +; If your hardware requires setting it explicitly, uncomment the +; HFILE block at the end. +; - Stage 1 does NOT initialize SP; stage 2 does (ld sp, $7FFF). +; - Blocks 1-5 are mapped in stage 2 (already running from page 8). ; -; Compilar: +; Compile: ; zxbasm bootstrap_stage1.asm -o bootstrap_stage1.bin -; o con pasmo: +; or with pasmo: ; pasmo --bin bootstrap_stage1.asm bootstrap_stage1.bin ; -; Cargar en el emulador / hardware a dirección $6000 (24576 decimal). -; Ejecutar desde BASIC con: +; Load on the emulator / hardware at address $6000 (24576 decimal). +; Run from BASIC with: ; RANDOMIZE USR 24576 ; =========================================================================== @@ -35,49 +35,49 @@ SD81_STAGE1: - di ; Interrupciones desactivadas + di ; Interrupts disabled ; -------------------------------------------------------------------------- - ; Asignar la direccion del framebuffer (HFILE) + ; Set the framebuffer address (HFILE) ; high=2044 low=(2043) ; - + ld hl, $C000 - ld ($07FB), hl - + ld ($07FB), hl + ; -------------------------------------------------------------------------- ; ------------------------------------------------------------------ - ; Activar modo Superfast HiRes Spectrum - ; POKE 2045, 172 → ld a, 172 / ld ($07FD), a - ; La FPGA del SD81 Booster interpreta esto como: - ; modo 172 ($AC) = Spectrum 256x192 desde HFILE=$C000 + ; Activate Superfast HiRes Spectrum mode + ; POKE 2045, 172 -> ld a, 172 / ld ($07FD), a + ; The SD81 Booster's FPGA interprets this as: + ; mode 172 ($AC) = Spectrum 256x192 from HFILE=$C000 ; ------------------------------------------------------------------ ld a, 172 ld ($07FD), a ; ------------------------------------------------------------------ - ; Desactivar IO mapeado en memoria - ; POKE 2056, 0 → ld ($0808), a - ; Evita colisiones entre las instrucciones IN/OUT y el espacio de RAM + ; Disable memory-mapped IO + ; POKE 2056, 0 -> ld ($0808), a + ; Avoids collisions between IN/OUT instructions and the RAM space ; ------------------------------------------------------------------ xor a ld ($0808), a ; ------------------------------------------------------------------ - ; Mapear bloque 0 ($0000-$1FFF) → página 8 del SD81 - ; Puerto $E7: modo full 64 páginas, OUT (C), A con B=página, A=bloque - ; La página 8 contiene el binario compilado (vectors + stage 2 + runtime) + ; Map block 0 ($0000-$1FFF) -> SD81 page 8 + ; Port $E7: full 64-page mode, OUT (C), A with B=page, A=block + ; Page 8 holds the compiled binary (vectors + stage 2 + runtime) ; ------------------------------------------------------------------ - ld b, 8 ; página SD81 destino (8 = primera página de usuario) - ld a, 0 ; bloque Z80 a reasignar (bloque 0 = $0000-$1FFF) - ld c, $E7 ; puerto de paginación SD81 - out (c), a ; mapear (B=página, A=bloque) + ld b, 8 ; destination SD81 page (8 = first user page) + ld a, 0 ; Z80 block to remap (block 0 = $0000-$1FFF) + ld c, $E7 ; SD81 paging port + out (c), a ; map (B=page, A=block) ; ------------------------------------------------------------------ - ; Saltar al stage 2 en la RAM recién mapeada - ; A partir de aquí el bloque 0 contiene la página 8: - ; $0000-$0067 vectores RST - ; $0100 __START_PROGRAM (inicio del stage 2) + ; Jump to stage 2 in the freshly mapped RAM + ; From here on, block 0 holds page 8: + ; $0000-$0067 RST vectors + ; $0100 __START_PROGRAM (stage 2's entry point) ; ------------------------------------------------------------------ jp $0100 diff --git a/src/arch/zx81sd/tools/split_sd81.py b/src/arch/zx81sd/tools/split_sd81.py index 5dfe7db79..51e29fc83 100644 --- a/src/arch/zx81sd/tools/split_sd81.py +++ b/src/arch/zx81sd/tools/split_sd81.py @@ -1,24 +1,24 @@ #!/usr/bin/env python3 """ -split_sd81.py — Particionador de binario ZX81+SD81 Booster + generador de loader +split_sd81.py — ZX81+SD81 Booster binary splitter + loader generator -Divide el binario plano generado por zxbc.py (--arch zx81sd, -f bin) -en páginas de 8KB para su carga desde el cargador BASIC del ZX81, y genera -el listado BASIC (texto plano, no tokenizado) necesario para cargarlas. +Splits the flat binary generated by zxbc.py (--arch zx81sd, -f bin) +into 8KB pages for loading from the ZX81 BASIC loader, and generates +the BASIC listing (plain text, not tokenized) needed to load them. -Mapa de páginas: - Página 8 → bloque 0 ($0000-$1FFF): vectors + stage2 + runtime + código usuario - Página 9 → bloque 1 ($2000-$3FFF): continuación si el binario supera 8KB - Página 10 → bloque 2 ($4000-$5FFF): ídem - Página 11 → bloque 3 ($6000-$7FFF): ídem (stage 1 externo reside aquí antes del salto) - Página 12 → bloque 4 ($8000-$9FFF): sysvars + heap (datos, no ejecutable sin MC45) - Página 13 → bloque 5 ($A000-$BFFF): heap continuación +Page map: + Page 8 -> block 0 ($0000-$1FFF): vectors + stage2 + runtime + user code + Page 9 -> block 1 ($2000-$3FFF): continuation if the binary exceeds 8KB + Page 10 -> block 2 ($4000-$5FFF): same + Page 11 -> block 3 ($6000-$7FFF): same (external stage 1 resides here before the jump) + Page 12 -> block 4 ($8000-$9FFF): sysvars + heap (data, not executable without MC45) + Page 13 -> block 5 ($A000-$BFFF): heap continuation -El binario empieza en $0000 y no incluye cabecera .tap/.tzx. -Cada fichero de salida se llama P.BIN (mayúsculas) donde N es la página. +The binary starts at $0000 and includes no .tap/.tzx header. Each +output file is named P.BIN (uppercase), where N is the page. -El loader BASIC generado (texto plano, aún no tokenizado a .p) sigue la -secuencia validada manualmente sobre hardware/emulador: +The generated BASIC loader (plain text, not yet tokenized to .p) +follows the sequence manually validated on hardware/emulator: 2 FAST 5 LOAD THEN CLEAR 24575 @@ -31,30 +31,35 @@ LOAD *MAP 7,63 RAND USR 24576 -Nota: "LOAD THEN CLEAR", "LOAD *MAP" y "LOAD FAST ... CODE" son extensiones -propias del firmware del SD81 Booster sobre el BASIC del ZX81 (no existen en -la ROM original). El bloque 7 ($E000/57344) se usa como ventana de paginación -temporal para volcar cada página física antes de que BOOT1.BIN haga el mapeo -definitivo de bloques 0-5 vía el puerto $E7. El "LOAD *MAP 7,63" final fuerza -el cambio a modo full-paging (64 páginas) y deja el bloque 7 en un estado -neutro antes de saltar al programa. +Note: "LOAD THEN CLEAR", "LOAD *MAP" and "LOAD FAST ... CODE" are +extensions of the SD81 Booster firmware over the ZX81's BASIC (they +don't exist in the original ROM). Block 7 ($E000/57344) is used as a +temporary paging window to dump each physical page before BOOT1.BIN +does the final mapping of blocks 0-5 through port $E7. The final +"LOAD *MAP 7,63" forces the switch to full-paging mode (64 pages) and +leaves block 7 in a neutral state before jumping to the program. -Además del listado en texto plano, genera el .p tokenizado real (listo para -cargar/ejecutar) usando zx81_p_loader.py, un puerto a Python del tokenizador -de EightyOne (zx81BasicLoader.cpp / IBasicLoader.cpp). +Besides the plain-text listing, this also generates the real tokenized +.p file (ready to load/run) using zx81_p_loader.py, a Python port of +EightyOne's tokenizer (zx81BasicLoader.cpp / IBasicLoader.cpp). -Uso: - python split_sd81.py [salida_base] +To load and run the resulting package on the ZX81 (or in EightyOne +pointing at the SD image): `LOAD FAST ""` — nothing else needs +selecting afterwards, it runs straight from there. - Si no se especifica salida_base, se usa el nombre de entrada sin extensión. +Usage: + python split_sd81.py [output_base] -Ejemplo: + If output_base isn't given, the input's name without extension is + used. + +Example: python split_sd81.py test_sd81.bin - → TEST_SD81P8.BIN (primera página, siempre presente) - → TEST_SD81P9.BIN (solo si el binario supera 8 KB) - → ... - → test_sd81_loader.txt (listado BASIC del cargador, texto plano) - → TEST_SD81.P (mismo listado, tokenizado) + -> TEST_SD81P8.BIN (first page, always present) + -> TEST_SD81P9.BIN (only if the binary exceeds 8 KB) + -> ... + -> test_sd81_loader.txt (loader's BASIC listing, plain text) + -> TEST_SD81.P (the same listing, tokenized) """ import sys @@ -62,13 +67,13 @@ from zx81_p_loader import build_p_file -PAGE_SIZE = 8192 # 8KB por página -FIRST_PAGE = 8 # página SD81 asignada al bloque 0 -CLEANUP_PAGE = 63 # página neutra: fuerza modo full-paging (64 páginas) +PAGE_SIZE = 8192 # 8KB per page +FIRST_PAGE = 8 # SD81 page assigned to block 0 +CLEANUP_PAGE = 63 # neutral page: forces full-paging mode (64 pages) BOOT1_FILE = "BOOT1.BIN" BOOT1_ADDR = 24576 # $6000 -CLEAR_ADDR = 24575 # protege BOOT1.BIN (reservado justo debajo) -LOAD_WINDOW_ADDR = 57344 # $E000, ventana de bloque 7 +CLEAR_ADDR = 24575 # protects BOOT1.BIN (reserved right below it) +LOAD_WINDOW_ADDR = 57344 # $E000, block 7's window def split(input_path: str, output_base: str) -> list[str]: @@ -76,7 +81,7 @@ def split(input_path: str, output_base: str) -> list[str]: data = f.read() if not data: - sys.exit(f"Error: {input_path} está vacío") + sys.exit(f"Error: {input_path} is empty") pages_written = [] page_num = FIRST_PAGE @@ -85,7 +90,7 @@ def split(input_path: str, output_base: str) -> list[str]: while offset < len(data): chunk = data[offset : offset + PAGE_SIZE] - # Rellenar hasta PAGE_SIZE con 0xFF (valor indefinido de FLASH/RAM sin inicializar) + # Pad up to PAGE_SIZE with 0xFF (undefined value for unwritten FLASH/RAM) if len(chunk) < PAGE_SIZE: chunk = chunk + b"\xff" * (PAGE_SIZE - len(chunk)) @@ -95,7 +100,7 @@ def split(input_path: str, output_base: str) -> list[str]: pages_written.append(out_path) print( - f" Pagina {page_num} (bloque {page_num - FIRST_PAGE}): " + f" Page {page_num} (block {page_num - FIRST_PAGE}): " f"{out_path} " f"[{offset:#06x} - {min(offset + PAGE_SIZE - 1, len(data) - 1):#06x}]" ) @@ -138,44 +143,44 @@ def main(): output_base = sys.argv[2] if len(sys.argv) > 2 else os.path.splitext(input_path)[0] output_base = os.path.basename(output_base).upper() - # Los nombres de fichero de las paginas y del loader.p se escriben en el - # charset del ZX81 (letras, digitos y unos pocos simbolos: sin guion - # bajo ni otros caracteres no representables). Fallar pronto y claro en - # vez de que reviente mas tarde al tokenizar el loader. + # Page and loader.p file names are written in the ZX81 charset + # (letters, digits and a few symbols: no underscore or other + # non-representable characters). Fail early and clearly instead of + # blowing up later while tokenizing the loader. from zx81_p_loader import ascii_to_zx for c in output_base: try: ascii_to_zx(c) except ValueError: sys.exit( - f"Error: el nombre base {output_base!r} contiene {c!r}, " - "no representable en el charset ZX81 (evita '_' y similares)." + f"Error: base name {output_base!r} contains {c!r}, " + "not representable in the ZX81 charset (avoid '_' and similar)." ) if not os.path.isfile(input_path): - sys.exit(f"Error: no se encuentra {input_path!r}") + sys.exit(f"Error: {input_path!r} not found") size = os.path.getsize(input_path) num_pages = (size + PAGE_SIZE - 1) // PAGE_SIZE - print(f"Binario: {input_path} ({size} bytes, {size/1024:.1f} KB, {num_pages} pagina/s)") - print(f"Particionando en paginas de {PAGE_SIZE} bytes (pagina SD81 inicial = {FIRST_PAGE})...") + print(f"Binary: {input_path} ({size} bytes, {size/1024:.1f} KB, {num_pages} page(s))") + print(f"Splitting into {PAGE_SIZE}-byte pages (starting SD81 page = {FIRST_PAGE})...") print() pages = split(input_path, output_base) print() - print(f"Generados {len(pages)} fichero/s.") + print(f"Generated {len(pages)} file(s).") if len(pages) == 1: - print("El programa cabe en una sola pagina (8 KB).") + print("The program fits in a single page (8 KB).") else: - print(f"Paginas {FIRST_PAGE} a {FIRST_PAGE + len(pages) - 1}.") + print(f"Pages {FIRST_PAGE} to {FIRST_PAGE + len(pages) - 1}.") loader_lines = generate_loader_lines(pages) loader_text = generate_loader_text(loader_lines) - # El .p es un nombre de fichero real para el ZX81 (charset sin guion - # bajo): se deriva de output_base (mismo prefijo que las paginas), - # concatenado sin separador, igual que "P8.BIN". + # The .p is a real file name for the ZX81 (charset with no + # underscore): derived from output_base (same prefix as the + # pages), concatenated with no separator, just like "P8.BIN". base_no_ext = os.path.splitext(input_path)[0] loader_txt_path = f"{base_no_ext}_loader.txt" loader_p_path = f"{output_base}.P" @@ -188,17 +193,17 @@ def main(): f.write(p_data) print() - print(f"Loader BASIC (texto plano): {loader_txt_path}") + print(f"BASIC loader (plain text): {loader_txt_path}") print("---") print(loader_text, end="") print("---") - print(f"Loader BASIC tokenizado (.p): {loader_p_path} ({len(p_data)} bytes)") + print(f"Tokenized BASIC loader (.p): {loader_p_path} ({len(p_data)} bytes)") print() - print("NOTA: 'LOAD THEN CLEAR', 'LOAD *MAP' y 'LOAD FAST ... CODE' son extensiones") - print("del firmware SD81 Booster, codificadas con el mismo mecanismo de tokens que") - print("el BASIC estandar del ZX81 (ver zx81_p_loader.py).") - print(f"Copia {BOOT1_FILE}, los ficheros de pagina y {os.path.basename(loader_p_path)}") - print("a la tarjeta SD.") + print("NOTE: 'LOAD THEN CLEAR', 'LOAD *MAP' and 'LOAD FAST ... CODE' are") + print("extensions of the SD81 Booster firmware, encoded with the same token") + print("mechanism as standard ZX81 BASIC (see zx81_p_loader.py).") + print(f"Copy {BOOT1_FILE}, the page files and {os.path.basename(loader_p_path)}") + print("to the SD card.") if __name__ == "__main__": diff --git a/src/arch/zx81sd/tools/zx81_p_loader.py b/src/arch/zx81sd/tools/zx81_p_loader.py index 75dfb166d..a2976503b 100644 --- a/src/arch/zx81sd/tools/zx81_p_loader.py +++ b/src/arch/zx81sd/tools/zx81_p_loader.py @@ -1,39 +1,41 @@ #!/usr/bin/env python3 """ -zx81_p_loader.py — Generador de ficheros .p (BASIC tokenizado) para ZX81 +zx81_p_loader.py — .p file (tokenized BASIC) generator for the ZX81 -Puerto directo a Python del algoritmo de tokenización usado por el emulador -EightyOne (zx81BasicLoader.cpp / IBasicLoader.cpp), referencia: +Direct Python port of the tokenization algorithm used by the EightyOne +emulator (zx81BasicLoader.cpp / IBasicLoader.cpp), reference: C:\\ClaudeCode\\Eightyone2\\src\\zx81\\zx81BasicLoader.cpp C:\\ClaudeCode\\Eightyone2\\src\\BasicLoader\\IBasicLoader.cpp -Los comandos extendidos del firmware SD81 Booster (LOAD THEN CLEAR, LOAD *MAP, -LOAD FAST ... CODE, etc.) no necesitan tratamiento especial: aunque esa -sintaxis no la admite el intérprete de la ROM del ZX81, están compuestos por: - - Tokens estándar del ZX81 (THEN, CLEAR, FAST, CODE, LOAD...) en posiciones - que la ROM no usaría pero que SÍ se pueden teclear (p.ej. SHIFT+3 = THEN). - - Palabras sueltas tras un asterisco (p.ej. *MAP, *VER) que son letras - corrientes, no tokens. -Por tanto basta con tokenizar el texto igual que cualquier BASIC estándar: -el propio algoritmo genérico ya produce la codificación correcta. - -No implementa (no hace falta para este proyecto): etiquetas @label, bloques -numéricos [DEC:...]/[HEX:...]/[BIN:...], secuencias de escape \\xx, códigos -gráficos, ni "alternate keyword spelling". Documentados en IBasicLoader.cpp -si en el futuro hicieran falta. +The SD81 Booster firmware's extended commands (LOAD THEN CLEAR, +LOAD *MAP, LOAD FAST ... CODE, etc.) need no special handling: even +though the ZX81 ROM interpreter doesn't accept that syntax, they're +made up of: + - Standard ZX81 tokens (THEN, CLEAR, FAST, CODE, LOAD...) in + positions the ROM wouldn't use but that CAN be typed (e.g. + SHIFT+3 = THEN). + - Standalone words after an asterisk (e.g. *MAP, *VER) which are + plain letters, not tokens. +So it's enough to tokenize the text just like any standard BASIC: the +generic algorithm itself already produces the correct encoding. + +Not implemented (not needed for this project): @label tags, numeric +blocks [DEC:...]/[HEX:...]/[BIN:...], \\xx escape sequences, graphics +codes, or "alternate keyword spelling". Documented in IBasicLoader.cpp +in case they're ever needed. """ import math import re import struct -BLANK = "\x01" # marcador interno: posición ya consumida +BLANK = "\x01" # internal marker: position already consumed NEWLINE = 0x76 -NUMBER_MARK = 0x7E # precede a los 5 bytes de número embebido -DOUBLE_QUOTE = 0xC0 # comilla escapada ("") dentro de una cadena +NUMBER_MARK = 0x7E # precedes the 5 bytes of an embedded number +DOUBLE_QUOTE = 0xC0 # escaped quote ("") inside a string -# Tokens estándar ZX81 (de zx81BasicLoader::ExtractTokens, sin variantes -# "alternate spelling" ni extensiones ZXpand) +# Standard ZX81 tokens (from zx81BasicLoader::ExtractTokens, without +# "alternate spelling" variants or ZXpand extensions) TOKENS = { 64: "RND", 65: "INKEY$", @@ -104,7 +106,7 @@ def ascii_to_zx(c: str) -> int: - """Puerto de zx81BasicLoader::AsciiToZX.""" + """Port of zx81BasicLoader::AsciiToZX.""" if c.isalpha(): return (ord(c.upper()) - ord('A')) + 38 if c.isdigit(): @@ -118,11 +120,11 @@ def ascii_to_zx(c: str) -> int: if c in table: return table[c] - raise ValueError(f"Caracter invalido: {c!r}") + raise ValueError(f"Invalid character: {c!r}") def zx81_float(value: float) -> bytes: - """Puerto de zx81BasicLoader::OutputFloatingPointEncoding.""" + """Port of zx81BasicLoader::OutputFloatingPointEncoding.""" exponent = 0 mantissa = 0 @@ -151,22 +153,22 @@ def zx81_float(value: float) -> bytes: class _Line: - """Estado de trabajo para una linea BASIC (sin numero de linea), replica - los buffers mLineBuffer / mLineBufferOutput / mLineBufferPopulated. + """Working state for a BASIC line (without a line number), mirroring + the mLineBuffer / mLineBufferOutput / mLineBufferPopulated buffers. - Todos los arrays tienen longitud self.n + 1: el ultimo hueco (indice n) - es un relleno de seguridad para cuando un token consume el espacio - artificial añadido al final para detectar tokens sin espacio de cierre - en el texto original (igual que el buffer sobredimensionado de C++). + All arrays have length self.n + 1: the last slot (index n) is a + safety pad for when a token consumes the artificial space added at + the end to detect tokens with no closing space in the original + text (same as the oversized C++ buffer). """ def __init__(self, text: str): - content = " " + text # ReadLine antepone siempre un espacio + content = " " + text # ReadLine always prepends a space self.n = len(content) - self.buf = list(content) + [BLANK] # +1 hueco de seguridad + self.buf = list(content) + [BLANK] # +1 safety slot self.out = [BLANK] * (self.n + 1) self.populated = [False] * (self.n + 1) - # BlankLineStart: sin numero de linea embebido, deja 1 espacio real + # BlankLineStart: no embedded line number, leaves 1 real space self.buf[0] = ' ' @@ -175,7 +177,7 @@ def _mask_copy(chars: list) -> list: def _mask_out_strings(tokenised: list): - """Puerto de IBasicLoader::MaskOutStrings.""" + """Port of IBasicLoader::MaskOutStrings.""" text = "".join(tokenised) q1 = text.find('"') if q1 == -1: @@ -205,7 +207,7 @@ def _mask_out_strings(tokenised: list): def _mask_out_rem_contents(tokenised: list): - """Puerto de IBasicLoader::MaskOutRemContents.""" + """Port of IBasicLoader::MaskOutRemContents.""" text = "".join(tokenised) rem = " REM " pos = 0 @@ -227,8 +229,8 @@ def _mask_out_rem_contents(tokenised: list): def _extract_double_quote_characters(line: _Line): - """Puerto de zx81BasicLoader::ExtractDoubleQuoteCharacters (sin soporte - de REM tokenizado ni alternate spelling, no necesarios aqui).""" + """Port of zx81BasicLoader::ExtractDoubleQuoteCharacters (without + tokenized-REM support or alternate spelling, not needed here).""" buf = line.buf n = line.n within_quote = False @@ -252,9 +254,9 @@ def _extract_double_quote_characters(line: _Line): def _do_tokenise(line: _Line, tokenised: list): - """Puerto de IBasicLoader::DoTokenise. Recorre los tokens de codigo mas - alto a mas bajo (equivalente al reverse_iterator sobre un std::map), - mutando `tokenised` (busqueda) y line.buf/out/populated (resultado).""" + """Port of IBasicLoader::DoTokenise. Walks tokens from highest to + lowest code (equivalent to the reverse_iterator over a std::map), + mutating `tokenised` (search) and line.buf/out/populated (result).""" for token_code in sorted(TOKENS.keys(), reverse=True): token = TOKENS[token_code] len_token = len(token) @@ -278,7 +280,7 @@ def _do_tokenise(line: _Line, tokenised: list): while True: guard += 1 if guard > 10000: - raise RuntimeError(f"Bucle de tokenizacion sin fin para {token!r}") + raise RuntimeError(f"Endless tokenizing loop for {token!r}") text = "".join(tokenised) pos = text.find(token) @@ -292,8 +294,8 @@ def _do_tokenise(line: _Line, tokenised: list): or (end_pos >= len(tokenised)) or (not tokenised[end_pos].isalnum())) if not (prev_ok and next_ok): - # Coincidencia pegada a un identificador: no es el token, - # se "rompe" localmente para seguir buscando mas adelante. + # Match glued to an identifier: not the token, "break" + # it locally so the search keeps going further ahead. tokenised[pos] = '\x02' continue @@ -315,22 +317,23 @@ def _do_tokenise(line: _Line, tokenised: list): def _start_of_number(line: _Line, tokenised: list, index: int) -> bool: - """Determina si `index` es el inicio de un literal numerico. - - Difiere deliberadamente del original IBasicLoader::StartOfNumber en un - punto: aqui solo se mira el caracter INMEDIATAMENTE anterior (retrocede - sobre espacios reales pero se detiene tan pronto encuentra CUALQUIER - caracter que no sea espacio, sin seguir retrocediendo mas alla). El - original retrocedia sobre TODOS los espacios consecutivos y comprobaba - el caracter previo a todos ellos, lo cual en BASIC estandar es - equivalente (los tokens siempre consumen su propio espacio final como - BLANK, no como ' ' literal) pero falla en comandos custom del SD81 como - "*MAP 7,8": "MAP" no es un token reconocido, por lo que el espacio tras - el queda como ' ' literal, y el algoritmo original terminaba mirando la - 'P' de MAP (alfabetica) y decidia que "7" NO era un numero. En un ZX81 - real, cualquier digito tecleado tras un espacio genera su propio float - embebido sin importar que palabra le precede, asi que aqui se replica - ese comportamiento (mas simple y mas correcto para este caso). + """Determines whether `index` is the start of a numeric literal. + + Deliberately differs from the original + IBasicLoader::StartOfNumber on one point: here only the character + IMMEDIATELY before is checked (it walks back over real spaces but + stops as soon as it finds ANY non-space character, without walking + back further). The original walked back over ALL consecutive + spaces and checked the character before all of them, which in + standard BASIC is equivalent (tokens always consume their own + trailing space as BLANK, not as a literal ' ') but fails on SD81 + custom commands like "*MAP 7,8": "MAP" isn't a recognized token, + so the space after it stays as a literal ' ', and the original + algorithm ended up looking at the (alphabetic) 'P' of MAP and + decided "7" was NOT a number. On a real ZX81, any digit typed after + a space generates its own embedded float regardless of the word + preceding it, so that behavior is replicated here (simpler and more + correct for this case). """ if not (line.buf[index] == '.' or line.buf[index].isdigit()): return False @@ -349,8 +352,9 @@ def _start_of_number(line: _Line, tokenised: list, index: int) -> bool: def _output_embedded_number(line: _Line, index: int, out: bytearray) -> int: - """Puerto de IBasicLoader::OutputEmbeddedNumber. Devuelve el nuevo - indice (ya avanzado mas alla del numero, listo para `i += 1` fuera).""" + """Port of IBasicLoader::OutputEmbeddedNumber. Returns the new + index (already advanced past the number, ready for `i += 1` + outside).""" n = line.n text_no_spaces = "" @@ -360,7 +364,7 @@ def _output_embedded_number(line: _Line, index: int, out: bytearray) -> int: m = _NUMBER_RE.match(text_no_spaces) if not m or m.end() == 0: - raise ValueError(f"Numero invalido en posicion {index}: {text_no_spaces!r}") + raise ValueError(f"Invalid number at position {index}: {text_no_spaces!r}") number_len_no_spaces = m.end() value = float(m.group(0)) @@ -390,17 +394,18 @@ def _output_embedded_number(line: _Line, index: int, out: bytearray) -> int: def process_line_body(text: str) -> bytes: - """Tokeniza una linea BASIC (sin numero de linea) y devuelve los bytes - ya codificados (sin NEWLINE final, que se añade en encode_line).""" + """Tokenizes a BASIC line (without a line number) and returns the + already-encoded bytes (without the final NEWLINE, which is added + in encode_line).""" line = _Line(text) n = line.n _extract_double_quote_characters(line) - # Copia de trabajo para deteccion de limites de token: el contenido real - # (sin el hueco de seguridad de line.buf) + un espacio real extra para - # detectar tokens sin espacio de cierre en el texto original. Longitud - # n+1, igual que line.buf, para que los indices sigan alineados. + # Working copy for token boundary detection: the real content + # (without line.buf's safety slot) + one extra real space to + # detect tokens with no closing space in the original text. + # Length n+1, same as line.buf, so indices stay aligned. tokenised = list(line.buf[:n]) + [' '] _mask_out_strings(tokenised) @@ -408,7 +413,7 @@ def process_line_body(text: str) -> bytes: _do_tokenise(line, tokenised) - # Rellenar cualquier posicion no consumida con su codigo ZX81 directo + # Fill any unconsumed position with its direct ZX81 code for i in range(1, n): if line.buf[i] != BLANK and not line.populated[i]: line.out[i] = ascii_to_zx(line.buf[i]) @@ -433,14 +438,14 @@ def encode_line(line_number: int, text: str) -> bytes: def encode_program(lines: list) -> bytes: - """lines: lista de tuplas (numero_de_linea:int, texto:str).""" + """lines: list of (line_number:int, text:str) tuples.""" return b"".join(encode_line(n, t) for n, t in lines) def build_p_file(lines: list) -> bytes: - """Puerto de zx81BasicLoader::OutputStartOfProgramData + - ProcessLine(*) + OutputEndOfProgramData. Devuelve el contenido completo - del fichero .p (desde 0x4009).""" + """Port of zx81BasicLoader::OutputStartOfProgramData + + ProcessLine(*) + OutputEndOfProgramData. Returns the complete + contents of the .p file (from 0x4009).""" data = bytearray() def out_byte(b): @@ -485,12 +490,12 @@ def change_word(offset, w): out_byte(0xBC) # PR_CC out_word(0x1821) # S_POSN out_byte(0x40) # CDFLAG - data.extend([0x00] * 32) # PRBUFF (32 bytes vacios) + data.extend([0x00] * 32) # PRBUFF (32 empty bytes) out_byte(0x76) # PRBUFF (byte 33, NEWLINE) data.extend([0x00] * 30) # MEMBOT out_word(0x0000) # SPARE - # --- Lineas BASIC --- + # --- BASIC lines --- for line_number, text in lines: data.extend(encode_line(line_number, text)) @@ -498,10 +503,10 @@ def change_word(offset, w): START_OF_RAM = 16393 # 0x4009 dfile_address = START_OF_RAM + len(data) - data.extend([NEWLINE] * 25) # display file colapsado (vacio) + data.extend([NEWLINE] * 25) # collapsed display file (empty) vars_address = START_OF_RAM + len(data) - out_byte(0x80) # fin de VARS (sin variables) + out_byte(0x80) # end of VARS (no variables) eline_address = START_OF_RAM + len(data) @@ -512,7 +517,7 @@ def change_word(offset, w): change_word(13, vars_address + 5) # CH_ADD change_word(17, eline_address + 5) # STKBOT change_word(19, eline_address + 5) # STKEND - change_word(32, vars_address) # NXTLIN (= VARS => sin autorun) + change_word(32, vars_address) # NXTLIN (= VARS => no autorun) return bytes(data) @@ -520,7 +525,7 @@ def change_word(offset, w): if __name__ == "__main__": import sys - # Autotest rapido con el loader validado manualmente + # Quick self-test with the manually validated loader lines = [ (2, "FAST"), (5, "LOAD THEN CLEAR 24575"), @@ -538,7 +543,7 @@ def change_word(offset, w): with open(out_path, "wb") as f: f.write(p_data) - print(f"Generado {out_path} ({len(p_data)} bytes)") + print(f"Generated {out_path} ({len(p_data)} bytes)") for i in range(0, len(p_data), 16): chunk = p_data[i:i + 16] hexpart = " ".join(f"{b:02X}" for b in chunk) From 54d4d3650603b394dd4806de197f69a8a1f8c24e Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 12:52:19 +0200 Subject: [PATCH 17/27] zx81sd: mark print42.bas/print64.bas as confirmed on real hardware Co-Authored-By: Claude Sonnet 5 --- src/arch/zx81sd/README.md | 10 ++++------ src/arch/zx81sd/doc/BASIC_CHANGES.md | 3 +-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/arch/zx81sd/README.md b/src/arch/zx81sd/README.md index 3ed854eec..d2bbe9ef5 100644 --- a/src/arch/zx81sd/README.md +++ b/src/arch/zx81sd/README.md @@ -48,12 +48,10 @@ Pending / not audited, remaining screen utilities in the shared stdlib: - `screen.bas`: **does depend on the ROM** (`$2538`/`$5C65`/`$19E8`, fixed Spectrum routines and sysvars to read back a screen character) — will need a real override, not just an audit. -- `print42.bas`/`print64.bas`: **ported** (full override in `stdlib/`) - — fixed sysvars → zx81sd equivalents, and the screen/attribute base - constants are patched at runtime (self-modifying code) instead of - being fixed. Verified by simulation (legible pixel-by-pixel text, no - memory corruption); pending confirmation on the emulator/real - hardware. +- `print42.bas`/`print64.bas`: **ported and confirmed working on real + hardware** — fixed sysvars → zx81sd equivalents, and the screen/ + attribute base constants are patched at runtime (self-modifying + code) instead of being fixed. See [doc/BASIC_CHANGES.md](doc/BASIC_CHANGES.md) for the general pattern behind this kind of fix. The `maskedsprites.bas`/MSFS port diff --git a/src/arch/zx81sd/doc/BASIC_CHANGES.md b/src/arch/zx81sd/doc/BASIC_CHANGES.md index 13dde89f5..f7ce909d4 100644 --- a/src/arch/zx81sd/doc/BASIC_CHANGES.md +++ b/src/arch/zx81sd/doc/BASIC_CHANGES.md @@ -324,8 +324,7 @@ Verified by simulation (the usual Python harness): both test strings (`tests_debug/print4264.bas`, companion repository) render legibly pixel by pixel at the expected position, with no writes outside screen/attributes/the file's own variables, and the program reaches its -normal `__END_PROGRAM`/`HALT`. Still pending confirmation on the -emulator/real hardware. +normal `__END_PROGRAM`/`HALT`. **Confirmed working on real hardware.** --- From ecd94da361ff57ca8165212b3997e3b7790eda85 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 13:04:32 +0200 Subject: [PATCH 18/27] zx81sd: add MCU command examples (filesystem, WAV, VGM, PEG, joystick, F_*) New examples exercising the SD81 Booster MCU's command surface (stdlib/mcu.bas/joy.bas), all compile clean but not yet run on real hardware: - filesystem.bas (expanded): McuPwd/McuCd/McuDirPrint/McuMkdir/ McuRmdir/McuSave/McuCopy/McuMove/McuDel/McuFree - wavplayer.bas: McuLoad with a .WAV file (MCU plays it directly) - vgmplayer.bas: McuPlayVgm/McuContVgm/McuPauseVgm/McuStopVgm - pegdemo.bas: McuLoadPeg/McuPlayPeg/McuStopPeg (byte encoding unconfirmed on hardware, see BASIC_CHANGES.md) - joystick.bas: Joy() programmable joystick mapping - randomaccess.bas: McuFOpenZx/McuFSeek/McuFRead/McuFWrite/McuFClose Found along the way: stdlib/input.bas defines input() as a function (a$ = input(n)), not the native INPUT a$ statement; using the statement form after #include fails to compile. Documented in BASIC_CHANGES.md as a new pattern to watch for. Co-Authored-By: Claude Sonnet 5 --- examples/sd81/filesystem.bas | 66 +++++++++++++++++++++----- examples/sd81/joystick.bas | 29 ++++++++++++ examples/sd81/pegdemo.bas | 54 +++++++++++++++++++++ examples/sd81/randomaccess.bas | 70 ++++++++++++++++++++++++++++ examples/sd81/vgmplayer.bas | 50 ++++++++++++++++++++ examples/sd81/wavplayer.bas | 25 ++++++++++ src/arch/zx81sd/doc/BASIC_CHANGES.md | 51 ++++++++++++++++++++ 7 files changed, 334 insertions(+), 11 deletions(-) create mode 100644 examples/sd81/joystick.bas create mode 100644 examples/sd81/pegdemo.bas create mode 100644 examples/sd81/randomaccess.bas create mode 100644 examples/sd81/vgmplayer.bas create mode 100644 examples/sd81/wavplayer.bas diff --git a/examples/sd81/filesystem.bas b/examples/sd81/filesystem.bas index 5adce9089..b21451717 100644 --- a/examples/sd81/filesystem.bas +++ b/examples/sd81/filesystem.bas @@ -1,11 +1,55 @@ -#include -#include -dim version as ubyte -version = McuVersion() -print "SD81 OS version: ";version -print "Current dir: "; McuPwd() -if McuCd("/")=0 then print "Change to root dir" else print "error" -if McuCd("/test")=0 then print "Change to test dir" else print "error" -McuDirPrint("/test/*.*") -McuLoad("/DEMO.WAV",0) -input a$ \ No newline at end of file +' ---------------------------------------------------------------------- +' filesystem.bas — SD81 Booster MCU file/directory commands +' +' Demonstrates the filesystem-management wrappers in mcu.bas: PWD, CD, +' DIR, MKDIR/RMDIR, DEL, COPY/MOVE and FREE. All of them return the +' MCU's status byte (0 = success); see the manual's error table for +' the rest of the codes. +' ---------------------------------------------------------------------- + +#include +#include + +dim st as ubyte +dim buf(15) as ubyte +dim i as ubyte + +print "SD81 MCU version: "; McuVersion() +print + +print "Current dir: "; McuPwd() + +print "CD /: "; McuCd("/") +print "DIR *.*:" +McuDirPrint("*.*") +print + +print "MKDIR /SD81DEMO: "; McuMkdir("/SD81DEMO") +print "CD /SD81DEMO: "; McuCd("/SD81DEMO") +print "Current dir: "; McuPwd() + +' a tiny 16-byte file, just to have something to copy/move/delete +for i = 0 to 15 + poke @buf(0) + i, i +next i +st = McuSave("DEMO.BIN", @buf(0), 16) +print "SAVE DEMO.BIN: "; st + +print "COPY DEMO.BIN -> COPY.BIN: "; McuCopy("DEMO.BIN", "COPY.BIN") +print "MOVE COPY.BIN -> RENAMED.BIN: "; McuMove("COPY.BIN", "RENAMED.BIN") + +print "DIR *.*:" +McuDirPrint("*.*") + +print "DEL DEMO.BIN: "; McuDel("DEMO.BIN") +print "DEL RENAMED.BIN: "; McuDel("RENAMED.BIN") + +print "CD /: "; McuCd("/") +print "RMDIR /SD81DEMO: "; McuRmdir("/SD81DEMO") + +st = McuFree() +print "FREE: "; McuFreeFreeKb; "/"; McuFreeTotalKb; " KB, status "; st + +print +print "Press a key..." +a$ = input(20) diff --git a/examples/sd81/joystick.bas b/examples/sd81/joystick.bas new file mode 100644 index 000000000..7f521b32a --- /dev/null +++ b/examples/sd81/joystick.bas @@ -0,0 +1,29 @@ +' ---------------------------------------------------------------------- +' joystick.bas — Programmable joystick via the SD81 Booster MCU +' +' The SD81 Booster maps a physical joystick to keypresses. Joy(keys) +' (joy.bas, cmd 21) configures that mapping: keys must be exactly 5 +' characters, in order UP, DOWN, LEFT, RIGHT, FIRE. Letters (any case), +' digits and space are valid; space means "no key" for that direction. +' Returns the MCU's status byte (0 = OK, 14 = invalid parameter). +' +' Once configured, the joystick just behaves like a keyboard: read it +' with INKEY$/INPUT as usual. +' ---------------------------------------------------------------------- + +#include + +dim st as ubyte +dim k$ as string + +' classic "QAOP + space" layout +st = Joy("QAOPM") +print "Joy(QAOPM) -> status "; st + +print "Move the joystick (BREAK to exit)" +do + k$ = inkey$ + if k$ <> "" then + print k$; + end if +loop diff --git a/examples/sd81/pegdemo.bas b/examples/sd81/pegdemo.bas new file mode 100644 index 000000000..83d43f820 --- /dev/null +++ b/examples/sd81/pegdemo.bas @@ -0,0 +1,54 @@ +' ---------------------------------------------------------------------- +' pegdemo.bas — PEG (Programmable Effects Generator) demo +' +' The PEG is a small virtual machine inside the SD81 Booster's MCU: it +' runs sound-effect programs on its own, driving the AY registers +' directly, with zero Z80 CPU cost. Up to 3 threads (0-2) can run at +' once. See the SD81 Booster manual, Appendix B, for the instruction +' set; McuLoadPeg's raw byte format is documented in mcu.bas ("2 bytes +' per instruction, little-endian"). +' +' This program below is a simple one-shot "beep": set AY channel A's +' tone period, enable tone on the mixer, set full volume, hold for +' ~200ms, then silence and halt. +' LD R0, $30 ; tone period A, low byte +' LD R1, $00 ; tone period A, high byte +' LD R7, $3E ; mixer: tone A enabled, everything else disabled +' LD R8, $0F ; channel A volume = max, no envelope +' WAIT 200 ; hold for 200 ms +' LD R8, $00 ; silence +' HALT +' +' NOTE: this exact byte encoding hasn't been confirmed on real hardware +' yet -- if it doesn't produce sound, double check the byte order +' against a working .PEB file or the peg.py assembler mentioned in the +' manual. +' ---------------------------------------------------------------------- + +#include +#include + +dim beep$ as string + +beep$ = chr($30) + chr($00) + _ + chr($00) + chr($01) + _ + chr($3E) + chr($07) + _ + chr($0F) + chr($08) + _ + chr($C8) + chr($90) + _ + chr($00) + chr($08) + _ + chr($10) + chr($A0) + +print "Loading PEG program at address 0..." +McuLoadPeg(0, beep$) + +print "Playing on thread 0..." +McuPlayPeg(0, 0) + +pause 100 + +print "Stopping thread 0." +McuStopPeg(0) + +print +print "Press a key..." +a$ = input(20) diff --git a/examples/sd81/randomaccess.bas b/examples/sd81/randomaccess.bas new file mode 100644 index 000000000..e2ee44e97 --- /dev/null +++ b/examples/sd81/randomaccess.bas @@ -0,0 +1,70 @@ +' ---------------------------------------------------------------------- +' randomaccess.bas — Random file access via the SD81 Booster MCU +' +' Demonstrates the F_* handle-based commands in mcu.bas: F_OPEN, +' F_SEEK, F_READ, F_WRITE and F_CLOSE. Unlike McuLoad/McuSave (which +' always transfer the whole file), these let a program seek to an +' arbitrary offset and read or write just part of a file -- useful for +' things like save-game slots, tile/level data files, or databases too +' big to keep fully in RAM. +' ---------------------------------------------------------------------- + +#include +#include + +dim h as ubyte +dim st as ubyte +dim buf(63) as ubyte +dim i as uinteger +dim ok as ubyte + +print "Creating a 64-byte test file..." +for i = 0 to 63 + poke @buf(0) + i, cast(ubyte, 200 - i) +next i +st = McuSave("RNDACC.BIN", @buf(0), 64) +print "SAVE: status "; st + +print "Opening it..." +h = McuFOpenZx("RNDACC.BIN") +print "F_OPEN: handle "; h + +print "Seeking to offset 32 and reading 8 bytes..." +st = McuFSeek(h, 32) +for i = 0 to 7 + poke @buf(0) + i, 0 +next i +st = McuFRead(h, @buf(0), 8) +ok = 1 +for i = 0 to 7 + if peek(@buf(0) + i) <> 200 - (32 + i) then + ok = 0 + end if +next i +if ok = 1 then + print "Read back correctly." +else + print "Read verification FAILED." +end if + +print "Overwriting the first 4 bytes and reading them back..." +st = McuFSeek(h, 0) +poke @buf(32), 11 +poke @buf(33), 22 +poke @buf(34), 33 +poke @buf(35), 44 +st = McuFWrite(h, @buf(32), 4) +st = McuFSeek(h, 0) +st = McuFRead(h, @buf(40), 4) +if peek(@buf(40)) = 11 and peek(@buf(43)) = 44 then + print "Overwrite verified." +else + print "Overwrite verification FAILED." +end if + +print "F_CLOSE: status "; McuFClose(h) +print "Deleting test file: status "; McuDel("RNDACC.BIN") + +print +print "Press a key..." +a$ = input(20) diff --git a/examples/sd81/vgmplayer.bas b/examples/sd81/vgmplayer.bas new file mode 100644 index 000000000..cddb821ae --- /dev/null +++ b/examples/sd81/vgmplayer.bas @@ -0,0 +1,50 @@ +' ---------------------------------------------------------------------- +' vgmplayer.bas — VGM music playback via the SD81 Booster MCU +' +' The MCU's built-in VGM player runs entirely in the background (only +' AY-3-8910/8912 data is supported): the Z80 program keeps running +' while the tune plays. McuPlayVgm() opens/prepares the file (adding +' .vgm if no extension is given); playback only actually starts once +' McuContVgm() is called. +' +' Change VGM_NAME below to a file that actually exists on your SD card. +' ---------------------------------------------------------------------- + +#include +#include + +const VGM_NAME as string = "MUSIC.VGM" + +dim st as ubyte +dim i as ubyte + +print "Loading "; VGM_NAME; "..." +st = McuPlayVgm(VGM_NAME) +print "MCU status: "; st + +print "Starting playback..." +McuContVgm() + +' let it play for a few seconds while the Z80 keeps doing its own thing +for i = 1 to 5 + print "playing... "; i + pause 50 +next i + +print "Pausing 2 seconds..." +McuPauseVgm() +pause 100 +print "Resuming..." +McuContVgm() + +for i = 1 to 5 + print "playing... "; i + pause 50 +next i + +print "Stopping." +McuStopVgm() + +print +print "Press a key..." +a$ = input(20) diff --git a/examples/sd81/wavplayer.bas b/examples/sd81/wavplayer.bas new file mode 100644 index 000000000..c4dc197aa --- /dev/null +++ b/examples/sd81/wavplayer.bas @@ -0,0 +1,25 @@ +' ---------------------------------------------------------------------- +' wavplayer.bas — WAV audio playback via the SD81 Booster MCU +' +' The MCU's LOAD command (cmd 9, wrapped here as McuLoad) recognises +' the .WAV extension automatically: instead of loading the file into +' memory it plays it directly (uncompressed PCM, 8-bit mono, 11025 Hz +' per the manual). McuLoad returns 0 bytes in that case; the real +' result is in McuStatus. +' +' Change WAV_NAME below to a file that actually exists on your SD card +' (with or without the .WAV extension). +' ---------------------------------------------------------------------- + +#include +#include + +const WAV_NAME as string = "DEMO.WAV" + +print "Playing "; WAV_NAME; " ..." +McuLoad(WAV_NAME, 0) +print "Done. MCU status: "; McuStatus + +print +print "Press a key..." +a$ = input(20) diff --git a/src/arch/zx81sd/doc/BASIC_CHANGES.md b/src/arch/zx81sd/doc/BASIC_CHANGES.md index f7ce909d4..09c250e5b 100644 --- a/src/arch/zx81sd/doc/BASIC_CHANGES.md +++ b/src/arch/zx81sd/doc/BASIC_CHANGES.md @@ -328,6 +328,51 @@ normal `__END_PROGRAM`/`HALT`. **Confirmed working on real hardware.** --- +## 10. MCU command examples (unofficial, no example to adapt) + +Six new example programs demonstrating the SD81 Booster MCU's command +surface (`stdlib/mcu.bas`/`joy.bas`), written directly for zx81sd — +none of them adapt an official `examples/` program. `examples/sd81/ +filesystem.bas` already existed from an earlier session (it's what the +companion repository's `files.bas`/`FILES*` binaries were compiled +from) and was expanded to also demo `McuMkdir`/`McuRmdir`/`McuCopy`/ +`McuMove`/`McuFree`, not just `McuCd`/`McuDirPrint`/`McuLoad`. + +| File | MCU commands exercised | Status | +|---|---|---| +| `examples/sd81/filesystem.bas` | `McuPwd`, `McuCd`, `McuDirPrint`, `McuMkdir`, `McuRmdir`, `McuSave`, `McuCopy`, `McuMove`, `McuDel`, `McuFree` | Compiles clean; not yet run on hardware | +| `examples/sd81/wavplayer.bas` | `McuLoad` with a `.WAV` file (played directly by the MCU, not loaded into memory) | Compiles clean; not yet run on hardware | +| `examples/sd81/vgmplayer.bas` | `McuPlayVgm`, `McuContVgm`, `McuPauseVgm`, `McuStopVgm` | Compiles clean; not yet run on hardware | +| `examples/sd81/pegdemo.bas` | `McuLoadPeg`, `McuPlayPeg`, `McuStopPeg` | Compiles clean; **PEG byte encoding unconfirmed on hardware**, see note below | +| `examples/sd81/joystick.bas` | `Joy()` (joy.bas, MCU cmd 21) | Compiles clean; not yet run on hardware | +| `examples/sd81/randomaccess.bas` | `McuFOpenZx`, `McuFSeek`, `McuFRead`, `McuFWrite`, `McuFClose` | Compiles clean; not yet run on hardware | + +Memory-mapper access (`Map()`/`MapGet()`) already has a dedicated +example, `examples/sd81/block7test.bas` (see section 8 above), so no +new one was added for that. + +**Compile-time bug found while writing these**: `stdlib/input.bas` +defines `input()` as a **function** (`a$ = input(maxChars)`), not the +native BASIC `INPUT` statement — using `input a$` (statement syntax) +after `#include ` produces +`error: Cannot convert string to a value. Use VAL() function`, since +the identifier `input` is now bound to that function. All six examples +above end with `a$ = input(20)` to wait for a keypress, not +`input a$`. + +**PEG byte encoding, open question**: the SD81 Booster manual's +Appendix B documents the PEG instruction set as 16-bit words in +big-endian order (e.g. `LD R,XX` = `0R XX`), but `mcu.bas`'s own +comment on `McuLoadPeg` says the raw bytes it expects are +"2 bytes little-endian per instruction". `pegdemo.bas` follows the +`mcu.bas` comment literally (since that's the function actually being +called) — each instruction's value byte is sent before its opcode +byte. This hasn't been confirmed against a working `.PEB` file or the +`peg.py` assembler mentioned in the manual, so treat the exact byte +order as unverified until tested on hardware. + +--- + ## Summary for the manual | Example | zx81sd copy | Source changes | Compile flags | @@ -341,6 +386,7 @@ normal `__END_PROGRAM`/`HALT`. **Confirmed working on real hardware.** | pong.bas (unofficial) | `pong.bas` in `examples/sd81/` | 1 ASM line (VSYNC_TICK namespace) | none special | | block7test.bas (unofficial) | `block7test.bas` in `examples/sd81/` | n/a (written directly for zx81sd) | none special | | print42.bas/print64.bas (libraries) | not applicable (not examples) | none — the fix was in `stdlib/print42.bas`/`print64.bas` | none special | +| filesystem.bas, wavplayer.bas, vgmplayer.bas, pegdemo.bas, joystick.bas, randomaccess.bas (unofficial) | all in `examples/sd81/` | n/a (written directly for zx81sd) | none special | Pattern identified for future examples: @@ -365,3 +411,8 @@ Pattern identified for future examples: trusting that the rest of the runtime (`CLS`/`PRINT`) already included it. See the `print42.bas`/`print64.bas` case above and [PRECAUTIONS.md](PRECAUTIONS.md). +5. `stdlib/input.bas` defines `input()` as a function + (`a$ = input(maxChars)`), not the native `INPUT a$` statement — once + `#include ` is used, `input a$` fails with + `error: Cannot convert string to a value`. See the MCU examples case + above. From 46700e3046b1ae7abc144356b5d0ef02cd6ac146 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 13:33:17 +0200 Subject: [PATCH 19/27] zx81sd: mark MCU command examples as confirmed on real hardware All six (filesystem, wavplayer, vgmplayer, pegdemo, joystick, randomaccess) tested OK, including pegdemo's PEG byte encoding. Co-Authored-By: Claude Sonnet 5 --- src/arch/zx81sd/doc/BASIC_CHANGES.md | 32 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/arch/zx81sd/doc/BASIC_CHANGES.md b/src/arch/zx81sd/doc/BASIC_CHANGES.md index 09c250e5b..b5f2d2ea0 100644 --- a/src/arch/zx81sd/doc/BASIC_CHANGES.md +++ b/src/arch/zx81sd/doc/BASIC_CHANGES.md @@ -340,12 +340,12 @@ from) and was expanded to also demo `McuMkdir`/`McuRmdir`/`McuCopy`/ | File | MCU commands exercised | Status | |---|---|---| -| `examples/sd81/filesystem.bas` | `McuPwd`, `McuCd`, `McuDirPrint`, `McuMkdir`, `McuRmdir`, `McuSave`, `McuCopy`, `McuMove`, `McuDel`, `McuFree` | Compiles clean; not yet run on hardware | -| `examples/sd81/wavplayer.bas` | `McuLoad` with a `.WAV` file (played directly by the MCU, not loaded into memory) | Compiles clean; not yet run on hardware | -| `examples/sd81/vgmplayer.bas` | `McuPlayVgm`, `McuContVgm`, `McuPauseVgm`, `McuStopVgm` | Compiles clean; not yet run on hardware | -| `examples/sd81/pegdemo.bas` | `McuLoadPeg`, `McuPlayPeg`, `McuStopPeg` | Compiles clean; **PEG byte encoding unconfirmed on hardware**, see note below | -| `examples/sd81/joystick.bas` | `Joy()` (joy.bas, MCU cmd 21) | Compiles clean; not yet run on hardware | -| `examples/sd81/randomaccess.bas` | `McuFOpenZx`, `McuFSeek`, `McuFRead`, `McuFWrite`, `McuFClose` | Compiles clean; not yet run on hardware | +| `examples/sd81/filesystem.bas` | `McuPwd`, `McuCd`, `McuDirPrint`, `McuMkdir`, `McuRmdir`, `McuSave`, `McuCopy`, `McuMove`, `McuDel`, `McuFree` | **Confirmed working on real hardware** | +| `examples/sd81/wavplayer.bas` | `McuLoad` with a `.WAV` file (played directly by the MCU, not loaded into memory) | **Confirmed working on real hardware** | +| `examples/sd81/vgmplayer.bas` | `McuPlayVgm`, `McuContVgm`, `McuPauseVgm`, `McuStopVgm` | **Confirmed working on real hardware** | +| `examples/sd81/pegdemo.bas` | `McuLoadPeg`, `McuPlayPeg`, `McuStopPeg` | **Confirmed working on real hardware** (byte order below is correct) | +| `examples/sd81/joystick.bas` | `Joy()` (joy.bas, MCU cmd 21) | **Confirmed working on real hardware** | +| `examples/sd81/randomaccess.bas` | `McuFOpenZx`, `McuFSeek`, `McuFRead`, `McuFWrite`, `McuFClose` | **Confirmed working on real hardware** | Memory-mapper access (`Map()`/`MapGet()`) already has a dedicated example, `examples/sd81/block7test.bas` (see section 8 above), so no @@ -360,16 +360,16 @@ the identifier `input` is now bound to that function. All six examples above end with `a$ = input(20)` to wait for a keypress, not `input a$`. -**PEG byte encoding, open question**: the SD81 Booster manual's -Appendix B documents the PEG instruction set as 16-bit words in -big-endian order (e.g. `LD R,XX` = `0R XX`), but `mcu.bas`'s own -comment on `McuLoadPeg` says the raw bytes it expects are -"2 bytes little-endian per instruction". `pegdemo.bas` follows the -`mcu.bas` comment literally (since that's the function actually being -called) — each instruction's value byte is sent before its opcode -byte. This hasn't been confirmed against a working `.PEB` file or the -`peg.py` assembler mentioned in the manual, so treat the exact byte -order as unverified until tested on hardware. +**PEG byte encoding, resolved**: the SD81 Booster manual's Appendix B +documents the PEG instruction set as 16-bit words in big-endian order +(e.g. `LD R,XX` = `0R XX`), while `mcu.bas`'s own comment on +`McuLoadPeg` says the raw bytes it expects are "2 bytes little-endian +per instruction" — `pegdemo.bas` followed the `mcu.bas` comment +literally (value byte before opcode byte), and this has now been +**confirmed correct on real hardware**: the manual's big-endian +notation describes the instruction word's numeric value, while +`McuLoadPeg`'s wire format for cmd 40 is genuinely little-endian, as +documented in the library. --- From dbe0e9d8c235141ad692e05606382a2621dd76c0 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 15:33:26 +0200 Subject: [PATCH 20/27] zx81sd: fix ruff findings and formatting - split_sd81.py: sort/organize the import block - zx81_p_loader.py: drop redundant int() around math.floor() (RUF046) - ruff format on the three files touched this session ruff check/mypy now clean project-wide; the only remaining ruff format finding (src/zxbpp/zxbpp.py) is pre-existing and unrelated to zx81sd, left untouched. Co-Authored-By: Claude Sonnet 5 --- src/arch/zx81sd/backend/main.py | 48 +++---- src/arch/zx81sd/tools/split_sd81.py | 15 +- src/arch/zx81sd/tools/zx81_p_loader.py | 189 ++++++++++++++----------- 3 files changed, 133 insertions(+), 119 deletions(-) diff --git a/src/arch/zx81sd/backend/main.py b/src/arch/zx81sd/backend/main.py index a3d3ecc9a..6efddcb0b 100644 --- a/src/arch/zx81sd/backend/main.py +++ b/src/arch/zx81sd/backend/main.py @@ -27,11 +27,11 @@ # $D800-$DAFF screen attributes # $E000-$FFFF block 7 — data banking (maps, sprites...) -_ORG = 0x0000 # the binary starts at $0000 (vectors.asm pads up to $0100) +_ORG = 0x0000 # the binary starts at $0000 (vectors.asm pads up to $0100) _STAGE2_ENTRY = 0x0100 # stage 2 bootstrap's entry point -_HEAP_ADDR = 0x8100 # heap in the data area ($8100-$BFFF) -_HEAP_SIZE = 0x3EFF # ~15.75 KB +_HEAP_ADDR = 0x8100 # heap in the data area ($8100-$BFFF) +_HEAP_SIZE = 0x3EFF # ~15.75 KB # SD81 pages assigned to each block (loaded by BASIC before the jump) # Page 8 -> block 0 ($0000-$1FFF) <- stage 1 only maps this one @@ -42,7 +42,7 @@ # Page 13 -> block 5 ($A000-$BFFF) ┘ _PAGE_MAP = [ - (1, 9), # block 1 -> page 9 + (1, 9), # block 1 -> page 9 (2, 10), # block 2 -> page 10 (3, 11), # block 3 -> page 11 (4, 12), # block 4 -> page 12 @@ -50,7 +50,7 @@ ] _SD81_PAGE_PORT = 0xE7 # memory mapper port (full mode: OUT (C), A) -_STACK_TOP = 0x7FFF # stack at the top of the executable area +_STACK_TOP = 0x7FFF # stack at the top of the executable area def _map_block(block: int, page: int) -> list[str]: @@ -80,12 +80,9 @@ def init(self): OPTIONS.heap_size = _HEAP_SIZE OPTIONS.heap_address = _HEAP_ADDR - OPTIONS(Action.ADD_IF_NOT_DEFINED, name="heap_start_label", type=str, - default=f"{NAMESPACE}.ZXBASIC_MEM_HEAP") - OPTIONS(Action.ADD_IF_NOT_DEFINED, name="heap_size_label", type=str, - default=f"{NAMESPACE}.ZXBASIC_HEAP_SIZE") - OPTIONS(Action.ADD_IF_NOT_DEFINED, name="headerless", type=bool, - default=False, ignore_none=True) + OPTIONS(Action.ADD_IF_NOT_DEFINED, name="heap_start_label", type=str, default=f"{NAMESPACE}.ZXBASIC_MEM_HEAP") + OPTIONS(Action.ADD_IF_NOT_DEFINED, name="heap_size_label", type=str, default=f"{NAMESPACE}.ZXBASIC_HEAP_SIZE") + OPTIONS(Action.ADD_IF_NOT_DEFINED, name="headerless", type=bool, default=False, ignore_none=True) self._QUAD_TABLE.update( { @@ -119,43 +116,32 @@ def emit_prologue() -> list[str]: heap_init = [f"{common.DATA_LABEL}:"] if common.REQUIRES.intersection(common.MEMINITS) or f"{NAMESPACE}.__MEM_INIT" in common.INITS: - heap_init.append( - "; Defines HEAP SIZE\n" - + OPTIONS.heap_size_label + " EQU " + str(OPTIONS.heap_size) - ) + heap_init.append("; Defines HEAP SIZE\n" + OPTIONS.heap_size_label + " EQU " + str(OPTIONS.heap_size)) if OPTIONS.heap_address is None: heap_init.append(OPTIONS.heap_start_label + ":") heap_init.append(f"DEFS {OPTIONS.heap_size}") else: - heap_init.append( - "; Defines HEAP ADDRESS\n" - + OPTIONS.heap_start_label + f" EQU {OPTIONS.heap_address}" - ) + heap_init.append("; Defines HEAP ADDRESS\n" + OPTIONS.heap_start_label + f" EQU {OPTIONS.heap_address}") heap_init.append( "; Defines USER DATA Length in bytes\n" + f"{NAMESPACE}.ZXBASIC_USER_DATA_LEN" + f" EQU {common.DATA_END_LABEL} - {common.DATA_LABEL}" ) - heap_init.append( - f"{NAMESPACE}.__LABEL__.ZXBASIC_USER_DATA_LEN" - + f" EQU {NAMESPACE}.ZXBASIC_USER_DATA_LEN" - ) - heap_init.append( - f"{NAMESPACE}.__LABEL__.ZXBASIC_USER_DATA EQU {common.DATA_LABEL}" - ) + heap_init.append(f"{NAMESPACE}.__LABEL__.ZXBASIC_USER_DATA_LEN" + f" EQU {NAMESPACE}.ZXBASIC_USER_DATA_LEN") + heap_init.append(f"{NAMESPACE}.__LABEL__.ZXBASIC_USER_DATA EQU {common.DATA_LABEL}") # -- RST vector table ($0000-$00FF) --------------------------------- # Must be the very first thing in the binary. Each RST takes 8 bytes. # An absolute org is used for every entry: the assembler fills the # gaps with zeros, which is correct for a non-executable area. output = ["org $0000"] - output.append("jp $0100") # $0000: reset / RST 0 -> stage 2 + output.append("jp $0100") # $0000: reset / RST 0 -> stage 2 output.append("org $0008") - output.append("di") # $0008: RST $08 (Spectrum error handler) + output.append("di") # $0008: RST $08 (Spectrum error handler) output.append("halt") output.append("org $0010") - output.append("di") # $0010-$0037: unused RSTs + output.append("di") # $0010-$0037: unused RSTs output.append("halt") output.append("org $0018") output.append("di") @@ -169,10 +155,10 @@ def emit_prologue() -> list[str]: output.append("di") output.append("halt") output.append("org $0038") - output.append("di") # $0038: RST $38 IM1 — permanent DI, never reached + output.append("di") # $0038: RST $38 IM1 — permanent DI, never reached output.append("halt") output.append("org $0066") - output.append("retn") # $0066: NMI disabled, but the vector must exist + output.append("retn") # $0066: NMI disabled, but the vector must exist # -- Stage 2 bootstrap at $0100 ------------------------------------ output.append(f"org {_STAGE2_ENTRY}") diff --git a/src/arch/zx81sd/tools/split_sd81.py b/src/arch/zx81sd/tools/split_sd81.py index 51e29fc83..9f8bfc9e6 100644 --- a/src/arch/zx81sd/tools/split_sd81.py +++ b/src/arch/zx81sd/tools/split_sd81.py @@ -62,17 +62,17 @@ -> TEST_SD81.P (the same listing, tokenized) """ -import sys import os +import sys from zx81_p_loader import build_p_file -PAGE_SIZE = 8192 # 8KB per page -FIRST_PAGE = 8 # SD81 page assigned to block 0 -CLEANUP_PAGE = 63 # neutral page: forces full-paging mode (64 pages) +PAGE_SIZE = 8192 # 8KB per page +FIRST_PAGE = 8 # SD81 page assigned to block 0 +CLEANUP_PAGE = 63 # neutral page: forces full-paging mode (64 pages) BOOT1_FILE = "BOOT1.BIN" -BOOT1_ADDR = 24576 # $6000 -CLEAR_ADDR = 24575 # protects BOOT1.BIN (reserved right below it) +BOOT1_ADDR = 24576 # $6000 +CLEAR_ADDR = 24575 # protects BOOT1.BIN (reserved right below it) LOAD_WINDOW_ADDR = 57344 # $E000, block 7's window @@ -148,6 +148,7 @@ def main(): # non-representable characters). Fail early and clearly instead of # blowing up later while tokenizing the loader. from zx81_p_loader import ascii_to_zx + for c in output_base: try: ascii_to_zx(c) @@ -162,7 +163,7 @@ def main(): size = os.path.getsize(input_path) num_pages = (size + PAGE_SIZE - 1) // PAGE_SIZE - print(f"Binary: {input_path} ({size} bytes, {size/1024:.1f} KB, {num_pages} page(s))") + print(f"Binary: {input_path} ({size} bytes, {size / 1024:.1f} KB, {num_pages} page(s))") print(f"Splitting into {PAGE_SIZE}-byte pages (starting SD81 page = {FIRST_PAGE})...") print() diff --git a/src/arch/zx81sd/tools/zx81_p_loader.py b/src/arch/zx81sd/tools/zx81_p_loader.py index a2976503b..5b10bcdb2 100644 --- a/src/arch/zx81sd/tools/zx81_p_loader.py +++ b/src/arch/zx81sd/tools/zx81_p_loader.py @@ -29,10 +29,10 @@ import re import struct -BLANK = "\x01" # internal marker: position already consumed +BLANK = "\x01" # internal marker: position already consumed NEWLINE = 0x76 -NUMBER_MARK = 0x7E # precedes the 5 bytes of an embedded number -DOUBLE_QUOTE = 0xC0 # escaped quote ("") inside a string +NUMBER_MARK = 0x7E # precedes the 5 bytes of an embedded number +DOUBLE_QUOTE = 0xC0 # escaped quote ("") inside a string # Standard ZX81 tokens (from zx81BasicLoader::ExtractTokens, without # "alternate spelling" variants or ZXpand extensions) @@ -108,14 +108,29 @@ def ascii_to_zx(c: str) -> int: """Port of zx81BasicLoader::AsciiToZX.""" if c.isalpha(): - return (ord(c.upper()) - ord('A')) + 38 + return (ord(c.upper()) - ord("A")) + 38 if c.isdigit(): - return (ord(c) - ord('0')) + 28 + return (ord(c) - ord("0")) + 28 table = { - ' ': 0, '"': 11, '#': 12, '$': 13, ':': 14, '?': 15, - '(': 16, ')': 17, '-': 22, '+': 21, '*': 23, '/': 24, - '=': 20, '>': 18, '<': 19, ';': 25, ',': 26, '.': 27, + " ": 0, + '"': 11, + "#": 12, + "$": 13, + ":": 14, + "?": 15, + "(": 16, + ")": 17, + "-": 22, + "+": 21, + "*": 23, + "/": 24, + "=": 20, + ">": 18, + "<": 19, + ";": 25, + ",": 26, + ".": 27, } if c in table: return table[c] @@ -133,23 +148,25 @@ def zx81_float(value: float) -> bytes: if neg: value = -value - exponent = int(math.floor(1e-12 + (math.log(value) / math.log(2.0)))) + exponent = math.floor(1e-12 + (math.log(value) / math.log(2.0))) if exponent < -129 or exponent > 126: raise OverflowError("Number out of range") - mantissa_val = (value / (2.0 ** exponent)) - 1 + mantissa_val = (value / (2.0**exponent)) - 1 mantissa_val *= 0x80000000 - mantissa = int(math.floor(mantissa_val)) + mantissa = math.floor(mantissa_val) exponent += 129 - return bytes([ - exponent & 0xFF, - (mantissa >> 24) & 0xFF, - (mantissa >> 16) & 0xFF, - (mantissa >> 8) & 0xFF, - mantissa & 0xFF, - ]) + return bytes( + [ + exponent & 0xFF, + (mantissa >> 24) & 0xFF, + (mantissa >> 16) & 0xFF, + (mantissa >> 8) & 0xFF, + mantissa & 0xFF, + ] + ) class _Line: @@ -163,13 +180,13 @@ class _Line: """ def __init__(self, text: str): - content = " " + text # ReadLine always prepends a space + content = " " + text # ReadLine always prepends a space self.n = len(content) - self.buf = list(content) + [BLANK] # +1 safety slot + self.buf = list(content) + [BLANK] # +1 safety slot self.out = [BLANK] * (self.n + 1) self.populated = [False] * (self.n + 1) # BlankLineStart: no embedded line number, leaves 1 real space - self.buf[0] = ' ' + self.buf[0] = " " def _mask_copy(chars: list) -> list: @@ -196,7 +213,7 @@ def _mask_out_strings(tokenised: list): elif within: tokenised[i] = BLANK else: - rest = "".join(tokenised[i + 1:]) + rest = "".join(tokenised[i + 1 :]) nq = rest.find('"') if nq == -1: return @@ -264,16 +281,18 @@ def _do_tokenise(line: _Line, tokenised: list): start_char = token[0] end_char = token[-1] - token_begins_with_space = start_char == ' ' + token_begins_with_space = start_char == " " token_begins_with_alpha = start_char.isalpha() - token_ends_with_space = end_char == ' ' + token_ends_with_space = end_char == " " token_ends_with_alpha = end_char.isalpha() len_adjustment = 0 eff_len_token = len_token - if end_char in ('(', ')', '!', '"', "'", ',', ';', ':') or \ - (end_char == '#' and (len_token < 2 or token[-2] != ' ')) or \ - (end_char == '*' and token != "**"): + if ( + end_char in ("(", ")", "!", '"', "'", ",", ";", ":") + or (end_char == "#" and (len_token < 2 or token[-2] != " ")) + or (end_char == "*" and token != "**") + ): eff_len_token -= 1 guard = 0 @@ -287,16 +306,24 @@ def _do_tokenise(line: _Line, tokenised: list): if pos == -1: break - prev_ok = (token_begins_with_space or not token_begins_with_alpha - or (pos == 0) or (not tokenised[pos - 1].isalnum())) + prev_ok = ( + token_begins_with_space + or not token_begins_with_alpha + or (pos == 0) + or (not tokenised[pos - 1].isalnum()) + ) end_pos = pos + eff_len_token - next_ok = (token_ends_with_space or not token_ends_with_alpha - or (end_pos >= len(tokenised)) or (not tokenised[end_pos].isalnum())) + next_ok = ( + token_ends_with_space + or not token_ends_with_alpha + or (end_pos >= len(tokenised)) + or (not tokenised[end_pos].isalnum()) + ) if not (prev_ok and next_ok): # Match glued to an identifier: not the token, "break" # it locally so the search keeps going further ahead. - tokenised[pos] = '\x02' + tokenised[pos] = "\x02" continue start_offset = 1 if token_begins_with_space else 0 @@ -335,20 +362,20 @@ def _start_of_number(line: _Line, tokenised: list, index: int) -> bool: preceding it, so that behavior is replicated here (simpler and more correct for this case). """ - if not (line.buf[index] == '.' or line.buf[index].isdigit()): + if not (line.buf[index] == "." or line.buf[index].isdigit()): return False if index == 0: return True prev = line.buf[index - 1] - if prev == ' ': + if prev == " ": return True return not (prev.isalpha() or prev.isdigit()) -_NUMBER_RE = re.compile(r'[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?') +_NUMBER_RE = re.compile(r"[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?") def _output_embedded_number(line: _Line, index: int, out: bytearray) -> int: @@ -370,7 +397,7 @@ def _output_embedded_number(line: _Line, index: int, out: bytearray) -> int: with_spaces_index = 0 for _ in range(number_len_no_spaces): - while line.buf[index + with_spaces_index] in (' ', BLANK): + while line.buf[index + with_spaces_index] in (" ", BLANK): with_spaces_index += 1 with_spaces_index += 1 @@ -383,8 +410,8 @@ def _output_embedded_number(line: _Line, index: int, out: bytearray) -> int: out.append(chr_code) i += 1 - while i < n and line.buf[i] == ' ': - out.append(ascii_to_zx(' ')) + while i < n and line.buf[i] == " ": + out.append(ascii_to_zx(" ")) i += 1 out.append(NUMBER_MARK) @@ -406,7 +433,7 @@ def process_line_body(text: str) -> bytes: # (without line.buf's safety slot) + one extra real space to # detect tokens with no closing space in the original text. # Length n+1, same as line.buf, so indices stay aligned. - tokenised = list(line.buf[:n]) + [' '] + tokenised = list(line.buf[:n]) + [" "] _mask_out_strings(tokenised) _mask_out_rem_contents(tokenised) @@ -434,7 +461,7 @@ def process_line_body(text: str) -> bytes: def encode_line(line_number: int, text: str) -> bytes: body = process_line_body(text) + bytes([NEWLINE]) - return struct.pack('>H', line_number) + struct.pack('H", line_number) + struct.pack(" bytes: @@ -460,40 +487,40 @@ def change_word(offset, w): data[offset + 1] = (w >> 8) & 0xFF # --- OutputStartOfProgramData --- - out_byte(0x00) # VERSN - out_word(0x0000) # E_PPC - out_word(0x0000) # D_FILE - out_word(0x0000) # DF_CC - out_word(0x0000) # VARS - out_word(0x0000) # DEST - out_word(0x0000) # E_LINE - out_word(0x0000) # CH_ADD - out_word(0x0000) # X_PTR - out_word(0x0000) # STKBOT - out_word(0x0000) # STKEND - out_byte(0x00) # BERG - out_word(0x405D) # MEM - out_byte(0x00) # SPARE1 - out_byte(0x02) # DF_SZ - out_word(0x0000) # S_TOP - out_word(0xFFFF) # LAST_K - out_byte(0x00) # DBOUNC - out_byte(0x37) # MARGIN - out_word(0x0000) # NXTLIN - out_word(0x0000) # OLDPPC - out_byte(0x00) # FLAGX - out_word(0x0000) # STRLEN - out_word(0x0C8D) # T_ADDR - out_word(0x4321) # SEED - out_word(0xE6E0) # FRAMES - out_word(0x0000) # COORDS - out_byte(0xBC) # PR_CC - out_word(0x1821) # S_POSN - out_byte(0x40) # CDFLAG + out_byte(0x00) # VERSN + out_word(0x0000) # E_PPC + out_word(0x0000) # D_FILE + out_word(0x0000) # DF_CC + out_word(0x0000) # VARS + out_word(0x0000) # DEST + out_word(0x0000) # E_LINE + out_word(0x0000) # CH_ADD + out_word(0x0000) # X_PTR + out_word(0x0000) # STKBOT + out_word(0x0000) # STKEND + out_byte(0x00) # BERG + out_word(0x405D) # MEM + out_byte(0x00) # SPARE1 + out_byte(0x02) # DF_SZ + out_word(0x0000) # S_TOP + out_word(0xFFFF) # LAST_K + out_byte(0x00) # DBOUNC + out_byte(0x37) # MARGIN + out_word(0x0000) # NXTLIN + out_word(0x0000) # OLDPPC + out_byte(0x00) # FLAGX + out_word(0x0000) # STRLEN + out_word(0x0C8D) # T_ADDR + out_word(0x4321) # SEED + out_word(0xE6E0) # FRAMES + out_word(0x0000) # COORDS + out_byte(0xBC) # PR_CC + out_word(0x1821) # S_POSN + out_byte(0x40) # CDFLAG data.extend([0x00] * 32) # PRBUFF (32 empty bytes) - out_byte(0x76) # PRBUFF (byte 33, NEWLINE) + out_byte(0x76) # PRBUFF (byte 33, NEWLINE) data.extend([0x00] * 30) # MEMBOT - out_word(0x0000) # SPARE + out_word(0x0000) # SPARE # --- BASIC lines --- for line_number, text in lines: @@ -510,14 +537,14 @@ def change_word(offset, w): eline_address = START_OF_RAM + len(data) - change_word(3, dfile_address) # D_FILE - change_word(5, dfile_address + 1) # DF_CC - change_word(7, vars_address) # VARS - change_word(11, vars_address + 1) # E_LINE - change_word(13, vars_address + 5) # CH_ADD - change_word(17, eline_address + 5) # STKBOT - change_word(19, eline_address + 5) # STKEND - change_word(32, vars_address) # NXTLIN (= VARS => no autorun) + change_word(3, dfile_address) # D_FILE + change_word(5, dfile_address + 1) # DF_CC + change_word(7, vars_address) # VARS + change_word(11, vars_address + 1) # E_LINE + change_word(13, vars_address + 5) # CH_ADD + change_word(17, eline_address + 5) # STKBOT + change_word(19, eline_address + 5) # STKEND + change_word(32, vars_address) # NXTLIN (= VARS => no autorun) return bytes(data) @@ -545,6 +572,6 @@ def change_word(offset, w): print(f"Generated {out_path} ({len(p_data)} bytes)") for i in range(0, len(p_data), 16): - chunk = p_data[i:i + 16] + chunk = p_data[i : i + 16] hexpart = " ".join(f"{b:02X}" for b in chunk) print(f"{i:04X}: {hexpart}") From 5d0a02d25be0766d3d7ece067039e4ef119b7dbc Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 16:51:20 +0200 Subject: [PATCH 21/27] zx81sd: confirm examples/winscroll.bas needs no adaptation Audited: WinScrollRight/Left/Up/Down have no Spectrum ROM dependency (everything reads SCREEN_ADDR/SCREEN_ATTR_ADDR dynamically), and the sysvars.asm dependency goes through #require (a link-time tag, not a textual #include), so it doesn't risk the INCBIN-inline bug found while porting print42.bas/print64.bas. Compiles clean and verified by simulation (screen/attributes fully touched, clean HALT at __END_PROGRAM). Pending confirmation on real hardware. Co-Authored-By: Claude Sonnet 5 --- src/arch/zx81sd/README.md | 8 ++++---- src/arch/zx81sd/doc/BASIC_CHANGES.md | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/arch/zx81sd/README.md b/src/arch/zx81sd/README.md index d2bbe9ef5..8c1798898 100644 --- a/src/arch/zx81sd/README.md +++ b/src/arch/zx81sd/README.md @@ -37,10 +37,10 @@ mapper...) and LOAD/SAVE/VERIFY...CODE against SD. Pending / not audited, remaining screen utilities in the shared stdlib: -- `winscroll.bas`: believed to be already ported and tested, but not - confirmed by a formal audit (no override in `zx81sd/stdlib/` — if - true, it's because it didn't need one, like `scroll.bas` before its - fix, or `4inarow.bas`). +- `winscroll.bas`: audited — no ROM dependency, no override needed + (like `scroll.bas` before its fix, or `4inarow.bas`). Compiles clean + and verified by simulation (`examples/winscroll.bas`, no source + changes needed); pending confirmation on real hardware. - `putchars.bas`/`puttile.bas`: not audited or tested. A quick look at the source finds no Spectrum ROM/sysvar addresses (`putChars` fills a rectangle of characters, `putTile` places a 16×16 px tile), so diff --git a/src/arch/zx81sd/doc/BASIC_CHANGES.md b/src/arch/zx81sd/doc/BASIC_CHANGES.md index b5f2d2ea0..ed30868b5 100644 --- a/src/arch/zx81sd/doc/BASIC_CHANGES.md +++ b/src/arch/zx81sd/doc/BASIC_CHANGES.md @@ -166,6 +166,28 @@ implementation, same register contract as the ROM — see --- +## 5b. `examples/winscroll.bas` — NO source changes, no library override needed either + +Compiles with default flags: + +``` +python -m src.zxbc.zxbc winscroll.bas --arch zx81sd -o winscroll.bin +``` + +Unlike `scroll.bas`, `winscroll.bas` (character-cell window scroll: +`WinScrollRight/Left/Up/Down(row, col, width, height)`) has **no ROM +dependency at all** — it computes everything from `SCREEN_ADDR`/ +`SCREEN_ATTR_ADDR` (read dynamically, not hardcoded) and only pulls in +`sysvars.asm` via `#require` (a link-time dependency tag, not a +textual `#include` — it doesn't risk the `INCBIN`-inline bug described +in [PRECAUTIONS.md](PRECAUTIONS.md) section 8). No zx81sd override was +needed; the shared zx48k library is used as-is via the normal fallback +mechanism. Verified by simulation (screen and attribute area fully +touched, clean `HALT` at `__END_PROGRAM`, no illegal writes). Pending +confirmation on real hardware. + +--- + ## 6. `examples/maskedsprites.bas` → `examples/sd81/maskedsprites_sd81.bas` **In progress — still not fully working.** @@ -382,6 +404,7 @@ documented in the library. | flights.bas | `flights_sd81.bas` | 3 kinds of change (POKE removed, 4× PEEK COORDS, 8× key case) | none special | | 4inarow.bas | no copy needed | none | none special | | scroll.bas | no copy needed | none (the fix was in the `stdlib/scroll.bas` library) | none special | +| winscroll.bas | no copy needed | none (no library override needed either — no ROM dependency) | none special | | maskedsprites.bas | `maskedsprites_sd81.bas` | `WaitForNewFrame` rewritten (EI+HALT → VSYNC_TICK); `cb/maskedsprites.bas` library ported to the mapper (block 7) — **in progress, still not fully working** | none special | | pong.bas (unofficial) | `pong.bas` in `examples/sd81/` | 1 ASM line (VSYNC_TICK namespace) | none special | | block7test.bas (unofficial) | `block7test.bas` in `examples/sd81/` | n/a (written directly for zx81sd) | none special | From 470cfbffd4396a645c18971ccb3a11281c37968a Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 16:56:48 +0200 Subject: [PATCH 22/27] zx81sd: mark winscroll.bas as confirmed on real hardware Co-Authored-By: Claude Sonnet 5 --- src/arch/zx81sd/README.md | 8 ++++---- src/arch/zx81sd/doc/BASIC_CHANGES.md | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/arch/zx81sd/README.md b/src/arch/zx81sd/README.md index 8c1798898..155e33645 100644 --- a/src/arch/zx81sd/README.md +++ b/src/arch/zx81sd/README.md @@ -37,10 +37,10 @@ mapper...) and LOAD/SAVE/VERIFY...CODE against SD. Pending / not audited, remaining screen utilities in the shared stdlib: -- `winscroll.bas`: audited — no ROM dependency, no override needed - (like `scroll.bas` before its fix, or `4inarow.bas`). Compiles clean - and verified by simulation (`examples/winscroll.bas`, no source - changes needed); pending confirmation on real hardware. +- `winscroll.bas`: **confirmed working on real hardware** — no ROM + dependency, no override needed (like `scroll.bas` before its fix, or + `4inarow.bas`). No source changes needed either + (`examples/winscroll.bas` as-is). - `putchars.bas`/`puttile.bas`: not audited or tested. A quick look at the source finds no Spectrum ROM/sysvar addresses (`putChars` fills a rectangle of characters, `putTile` places a 16×16 px tile), so diff --git a/src/arch/zx81sd/doc/BASIC_CHANGES.md b/src/arch/zx81sd/doc/BASIC_CHANGES.md index ed30868b5..341489011 100644 --- a/src/arch/zx81sd/doc/BASIC_CHANGES.md +++ b/src/arch/zx81sd/doc/BASIC_CHANGES.md @@ -183,8 +183,8 @@ textual `#include` — it doesn't risk the `INCBIN`-inline bug described in [PRECAUTIONS.md](PRECAUTIONS.md) section 8). No zx81sd override was needed; the shared zx48k library is used as-is via the normal fallback mechanism. Verified by simulation (screen and attribute area fully -touched, clean `HALT` at `__END_PROGRAM`, no illegal writes). Pending -confirmation on real hardware. +touched, clean `HALT` at `__END_PROGRAM`, no illegal writes). +**Confirmed working on real hardware.** --- From 0fbb40000c4cf9e24d5b84e2ee038cac2e246d87 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 17:12:41 +0200 Subject: [PATCH 23/27] zx81sd: port puttile.bas, confirm putchars.bas needs no changes putchars.bas: audited, no ROM dependency (SCREEN_ADDR/SCREEN_ATTR_ADDR read dynamically, #require "sysvars.asm" is a safe link-time tag) -- no override needed, used as-is. puttile.bas: needed an override -- hardcodes the screen/attribute base as immediate constants (add a,64 / add a,88, the Spectrum ROM's fixed $4000/$5800 high bytes) instead of reading SCREEN_ADDR/ SCREEN_ATTR_ADDR. Same bug class as print42.bas/print64.bas; fixed the same way (self-modifying code patches both constants once on entering putTile(), reading the real high bytes at that point). New example examples/sd81/putcharstile.bas exercises putChars/paint/ putTile. Verified by simulation with breakpoints right after each function returns (a naive end-of-program check looked broken due to an unrelated screen scroll triggered by the demo's own final PRINT AT landing on the last screen row -- moved to row 20, false alarm). Pending confirmation on real hardware. Co-Authored-By: Claude Sonnet 5 --- examples/sd81/putcharstile.bas | 48 ++++ src/arch/zx81sd/README.md | 13 +- src/arch/zx81sd/doc/BASIC_CHANGES.md | 39 +++ src/lib/arch/zx81sd/stdlib/puttile.bas | 325 +++++++++++++++++++++++++ 4 files changed, 421 insertions(+), 4 deletions(-) create mode 100644 examples/sd81/putcharstile.bas create mode 100644 src/lib/arch/zx81sd/stdlib/puttile.bas diff --git a/examples/sd81/putcharstile.bas b/examples/sd81/putcharstile.bas new file mode 100644 index 000000000..3ed652596 --- /dev/null +++ b/examples/sd81/putcharstile.bas @@ -0,0 +1,48 @@ +' ---------------------------------------------------------------------- +' putcharstile.bas — putchars.bas (putChars/paint) and puttile.bas (putTile) +' +' putchars.bas: audited, no ROM dependency (everything reads +' SCREEN_ADDR/SCREEN_ATTR_ADDR dynamically) -- no zx81sd override +' needed, used as-is from the shared zx48k stdlib. +' +' puttile.bas: DID need a zx81sd override -- the original hardcodes the +' screen/attribute base as immediate constants (add a,64 / add a,88, +' the Spectrum ROM's fixed $4000/$5800 high bytes) instead of reading +' SCREEN_ADDR/SCREEN_ATTR_ADDR. See src/lib/arch/zx81sd/stdlib/puttile.bas. +' ---------------------------------------------------------------------- + +#include +#include +#include + +dim i as ubyte +dim charData(95) as ubyte +dim tileData(35) as ubyte + +' 12 character cells (4 wide x 3 tall), 8 bytes each: a houndstooth fill +for i = 0 to 95 + poke @charData(0) + i, 170 +next i + +' 16x16 tile: 16 pixel-rows (2 bytes each, one per column) + top/bottom attrs +for i = 0 to 15 + poke @tileData(0) + i * 2, 255 + poke @tileData(0) + i * 2 + 1, 129 +next i +poke @tileData(0) + 32, 56 +poke @tileData(0) + 33, 56 +poke @tileData(0) + 34, 63 +poke @tileData(0) + 35, 63 + +ink 7: paper 1: cls + +putChars(1, 1, 4, 3, @charData(0)) +paint(6, 1, 4, 3, 71) + +putTile(10, 10, @tileData(0)) + +' fixed-row PRINT away from the last screen row: avoids triggering a +' scroll (input()'s own cursor handling can push past row 23) that +' would shift the drawing above right before you get to look at it +print at 20, 0; "putChars/paint/putTile - press a key"; +a$ = input(20) diff --git a/src/arch/zx81sd/README.md b/src/arch/zx81sd/README.md index 155e33645..9e40683b0 100644 --- a/src/arch/zx81sd/README.md +++ b/src/arch/zx81sd/README.md @@ -41,10 +41,15 @@ Pending / not audited, remaining screen utilities in the shared stdlib: dependency, no override needed (like `scroll.bas` before its fix, or `4inarow.bas`). No source changes needed either (`examples/winscroll.bas` as-is). -- `putchars.bas`/`puttile.bas`: not audited or tested. A quick look at - the source finds no Spectrum ROM/sysvar addresses (`putChars` fills a - rectangle of characters, `putTile` places a 16×16 px tile), so - they're good candidates to work unchanged, but this isn't confirmed. +- `putchars.bas`: audited — no ROM dependency, no override needed. + Compiles clean and verified by simulation + (`examples/sd81/putcharstile.bas`); pending confirmation on real + hardware. +- `puttile.bas`: **did need a zx81sd override** — hardcoded screen/ + attribute base constants, same bug class as `print42.bas`/ + `print64.bas` (fixed the same way, self-modifying code patched at + runtime). Verified by simulation; pending confirmation on real + hardware. - `screen.bas`: **does depend on the ROM** (`$2538`/`$5C65`/`$19E8`, fixed Spectrum routines and sysvars to read back a screen character) — will need a real override, not just an audit. diff --git a/src/arch/zx81sd/doc/BASIC_CHANGES.md b/src/arch/zx81sd/doc/BASIC_CHANGES.md index 341489011..6a1053a7d 100644 --- a/src/arch/zx81sd/doc/BASIC_CHANGES.md +++ b/src/arch/zx81sd/doc/BASIC_CHANGES.md @@ -188,6 +188,44 @@ touched, clean `HALT` at `__END_PROGRAM`, no illegal writes). --- +## 5c. `putchars.bas`/`puttile.bas` — one clean, one needed a zx81sd override + +New example `examples/sd81/putcharstile.bas` (no official example to +adapt) exercises both libraries: + +- **`putchars.bas`** (`putChars`/`getChars`/`paint`/`paintData`/ + `getPaintData`/`putCharsOverMode`): audited, **no ROM dependency at + all** — every address is computed from `SCREEN_ADDR`/ + `SCREEN_ATTR_ADDR` read dynamically, and the file's own + `#require "sysvars.asm"` is the safe link-time tag (see section 5b + above), not a textual include. No zx81sd override needed; used as-is. +- **`puttile.bas`** (`putTile`, places a 16×16 px tile): **did need an + override**. Unlike `putchars.bas`, it hardcodes the screen/attribute + base as **immediate constants** — `add a,64` (screen, the Spectrum + ROM's `$4000` high byte) and `add a,88` (attributes, `$5800`'s high + byte) — instead of reading `SCREEN_ADDR`/`SCREEN_ATTR_ADDR`. Same bug + class as `print42.bas`/`print64.bas`. Fixed the same way: + `src/lib/arch/zx81sd/stdlib/puttile.bas` patches both constants once + on entering `putTile()` (self-modifying code on the `ADD A,n` + instructions themselves), reading the real high byte of + `SCREEN_ADDR`/`SCREEN_ATTR_ADDR` at that point. Everything else + (the interrupt save/restore dance, the stack-based pixel pushing) is + architecture-agnostic and copied unchanged — the `ld a,i` / `jp po` + idiom naturally never re-enables interrupts on zx81sd, since IFF2 is + always reset here (the runtime never does `EI`). + +**Debugging note**: the first simulation run of this example looked +broken (`paint()` seemingly painted only 1 of 3 rows, `putTile`'s area +looked empty) — turned out to be a false alarm caused by the example's +own final `PRINT AT` landing on the screen's last row (23), which +combined with `input()`'s cursor handling to trigger a normal scroll +that shifted the drawing up before the check ran. Moving the final +prompt to row 20 fixed it; `paint()`/`putTile()` were correct all +along. Verified with breakpoints right after each function returns +(not just at the end) to catch this class of false negative. + +--- + ## 6. `examples/maskedsprites.bas` → `examples/sd81/maskedsprites_sd81.bas` **In progress — still not fully working.** @@ -405,6 +443,7 @@ documented in the library. | 4inarow.bas | no copy needed | none | none special | | scroll.bas | no copy needed | none (the fix was in the `stdlib/scroll.bas` library) | none special | | winscroll.bas | no copy needed | none (no library override needed either — no ROM dependency) | none special | +| putchars.bas/puttile.bas (unofficial, `putcharstile.bas`) | `putcharstile.bas` in `examples/sd81/` | none for putchars.bas; `puttile.bas` needed a zx81sd override (hardcoded screen/attr base constants, same fix as print42/64) | none special | | maskedsprites.bas | `maskedsprites_sd81.bas` | `WaitForNewFrame` rewritten (EI+HALT → VSYNC_TICK); `cb/maskedsprites.bas` library ported to the mapper (block 7) — **in progress, still not fully working** | none special | | pong.bas (unofficial) | `pong.bas` in `examples/sd81/` | 1 ASM line (VSYNC_TICK namespace) | none special | | block7test.bas (unofficial) | `block7test.bas` in `examples/sd81/` | n/a (written directly for zx81sd) | none special | diff --git a/src/lib/arch/zx81sd/stdlib/puttile.bas b/src/lib/arch/zx81sd/stdlib/puttile.bas new file mode 100644 index 000000000..f94b18544 --- /dev/null +++ b/src/lib/arch/zx81sd/stdlib/puttile.bas @@ -0,0 +1,325 @@ +' ---------------------------------------------------------------- +' This file is released under the MIT License +' +' Copyleft (k) 2008-2023 +' by Paul Fisher (a.k.a. BritLion) +' ---------------------------------------------------------------- +' +' zx81sd override: the zx48k version computes the screen bitmap and +' attribute addresses with IMMEDIATE constants (`add a,64` / `add +' a,88`, the high bytes of the Spectrum ROM's fixed $4000/$5800), not +' by reading SCREEN_ADDR/SCREEN_ATTR_ADDR. On zx81sd the screen lives +' at $C000/$D800 (block 6, a variable, not a literal), so those two +' constants are wrong here -- same bug class found and fixed while +' porting print42.bas/print64.bas. Fixed by patching both constants +' once on entering putTile(), reading the real high byte of +' SCREEN_ADDR/SCREEN_ATTR_ADDR at that point (self-modifying code, +' same technique as print42.bas/print64.bas). Everything else in this +' file (the interrupt save/restore dance, the stack-based pixel +' pushing) is architecture-agnostic and copied unchanged: the +' `ld a,i` / `jp po` idiom naturally never re-enables interrupts on +' zx81sd, since IFF2 is always reset here (the runtime never does EI). +' ---------------------------------------------------------------- + +#ifndef __LIBRARY_PUTTILE__ + +REM Avoid recursive / multiple inclusion + +#define __LIBRARY_PUTTILE__ + +#pragma push(case_insensitive) +#pragma case_insensitive = True + + +' ---------------------------------------------------------------- +' SUB putTile +' +' Routine to place a 16 pixel by 16 pixel "Tile" onto the screen at character position x,y from adddress given. +' Data must be in the format of 16 bit rows, followed by attribute data. +' (c) 2010 Britlion, donated to the ZX BASIC project. +' Thanks to Boriel, LCD and Na_than for inspiration behind this. + +' This routine could be used as the basis for a fast sprite system, provided all sprites can be in 4 character blocks. +' It can also be used to clean up dirty background (erase sprites), or put backgrounds from tiled blocks onto a screen. +' +' Note the comments about Self Modifying code should be ignored. This has been updated with IX+n methods, which overall +' are faster than accessing and changing the code. +' (They would have to be accessed to change the memory anyway - may as well just access them directly.) + +' Parameters: +' x - x coordinate (cell column) +' y - y coordinate (cell row) +' graphicsAddr - Memory address of the graphicc +' +' ---------------------------------------------------------------- + +SUB putTile(x as uByte, y as uByte, graphicsAddr as uInteger) + ASM + PROC + LOCAL ptstackSave, pt_start, ptNextThird3, ptSameThird3 + LOCAL ptAddrDone3, ptNextSprite2, pt_nointerrupts + LOCAL PT_SCR_HI, PT_ATTR_HI + + ; Patch the two screen/attribute base constants below with the + ; real high byte of SCREEN_ADDR/SCREEN_ATTR_ADDR (variables on + ; zx81sd, not the Spectrum ROM's fixed $4000/$5800). + ld a, (.core.SCREEN_ADDR+1) + ld (PT_SCR_HI+1), a + ld a, (.core.SCREEN_ATTR_ADDR+1) + ld (PT_ATTR_HI+1), a + + jp pt_start + +ptstackSave: + defb 0,0 + +pt_start: + ld a,i + push af ; Save interrupt status. + + ; Routine to save the background to the buffer + di ; we REALLY can NOT be having interrupts while the stack and IX and IY are pointed elsewhere. + + push ix + push iy + ld d,(ix+9) + ld e,(ix+8) + ex de,hl + + ;; Print sprites routine + ld (ptstackSave), sp ; Save Stack Pointer + ld sp,hl ; now SP points at the start of the graphics. + + ; This function returns the address into HL of the screen address + ld a,(IX+5) ; Load in x - note the Self Modifying value + ld IYH, a ; save it + ld l,a + ld a,(IX+7) ; Load in y - note the Self Modifying value + ld IYL, a ; save it + ld d,a + and 24 +PT_SCR_HI: + add a,64 + ld h,a + ld a,d + and 7 + rrca + rrca + rrca + or l + add a,2 ; Need to be to the right so backwards writing pushes land properly. + ld l,a + + ; SO now, HL -> Screen address, and SP -> Graphics. Time to start loading. + + pop bc ; row 0 + pop de ; row 1 + ex af,af' + pop af ; row 2 + ex af,af' + exx + pop bc ; row 3 + pop de ; row 4 + pop hl ; row 5 + exx + + ; All right. We're loaded. Time to dump! + + ld ix,0 + add ix,sp ; Save our stack pointer into ix + + ld sp,hl ; point at the screen. + push bc ; row 0 + + inc h + ld sp,hl + push de ; row 1 + + inc h + ld sp,hl + ex af,af' + push af ; row 2 + + inc h + ld sp,hl + exx + push bc ; row 3 + exx + + inc h + ld sp,hl + exx + push de ; row 4 + exx + + inc h + ld sp,hl + exx + push hl ; row 5 + exx + + ; We're empty. Time to load up again. + + ld sp,ix + pop bc ; row 6 + pop de ; row 7 + ex af,af' + pop af ; row 8 + ex af,af' + exx + pop bc ; row 9 + pop de ; row 10 + pop hl ; row 11 + exx + + ; and we're loaded up again! Time to dump this graphic on the screen. + + ld ix,0 + add ix,sp ; save SP in IX + + inc h + ld sp,hl + push bc ; row 6 + + inc h + ld sp,hl + push de ; row 7 + + dec hl + dec hl + + ; Aha. Snag. We're at the bottom of a character. What's the next address down? + ld a,l + and 224 + cp 224 + jr nz, ptNextThird3 + +ptSameThird3: + ld de,32 + add hl,de + jp ptAddrDone3 + +ptNextThird3: + ld de,-1760 + add hl,de + +ptAddrDone3: + inc hl + inc hl + + ld sp,hl + ex af,af' + push af ; row 8 + + inc h + ld sp,hl + exx + push bc ; row 9 + exx + + inc h + ld sp,hl + exx + push de ; row 10 + exx + + inc h + ld sp,hl + exx + push hl ; row 11 + exx + + ; Okay. Registers empty. Reload time! + ld sp,ix + pop bc ; row 12 + pop de ; row 13 + + exx + pop bc ; row 14 + pop de ; row 15 + pop hl ; top attrs + exx + + ex af,af' + pop af ; bottom attrs + ex af,af' + + ; and the last dump to screen + + inc h + ld sp,hl + push bc + + inc h + ld sp,hl + push de + + inc h + ld sp,hl + exx + push bc + exx + + inc h + ld sp,hl + exx + push de + exx + + ; Pixels done. Just need to do the attributes. + ; So set HL to the attr address: + + ld a,iyl ;ypos + + rrca + rrca + rrca ; Multiply by 32 + ld l,a ; Pass to L + and 3 ; Mask with 00000011 +PT_ATTR_HI: + add a,88 ; patched with the real SCREEN_ATTR_ADDR high byte on entry + ld h,a ; Put it in the High Byte + ld a,l ; We get y value *32 + and 224 ; Mask with 11100000 + ld l,a ; Put it in L + ld a,iyh ; xpos + adc a,l ; Add it to the Low byte + ld l,a ; Put it back in L, and we're done. HL=Address. + inc hl ; we need to be to the right of the attr point as pushes write backwards. + inc hl + + ; attr + ld sp,hl + exx + push hl ; top row + exx + + ld hl,34 ; we need to move down to the next row. We already backed up 2, so we add 34. + add hl,sp + ld sp,hl + ex af,af' ; bottom row + push af + +ptNextSprite2: + ; done. Cleanup. + ld sp,(ptstackSave) ; put our stack back together. + + ; done all 4 final clean up + + pop iy + pop ix + + pop af ; recover interrupt status + jp po, pt_nointerrupts + ei ; Okay. We put everything back. If you need interrupts, you can go with em. + +pt_nointerrupts: + ENDP + END ASM +END SUB + +#require "sysvars.asm" + +#pragma pop(case_insensitive) + +#endif From 9f961b3538a41392252d99e4b4fb76822d898b70 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 17:20:26 +0200 Subject: [PATCH 24/27] zx81sd: mark putchars.bas/puttile.bas as confirmed on real hardware Co-Authored-By: Claude Sonnet 5 --- src/arch/zx81sd/README.md | 16 +++++++--------- src/arch/zx81sd/doc/BASIC_CHANGES.md | 1 + 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/arch/zx81sd/README.md b/src/arch/zx81sd/README.md index 9e40683b0..8e781eb6c 100644 --- a/src/arch/zx81sd/README.md +++ b/src/arch/zx81sd/README.md @@ -41,15 +41,13 @@ Pending / not audited, remaining screen utilities in the shared stdlib: dependency, no override needed (like `scroll.bas` before its fix, or `4inarow.bas`). No source changes needed either (`examples/winscroll.bas` as-is). -- `putchars.bas`: audited — no ROM dependency, no override needed. - Compiles clean and verified by simulation - (`examples/sd81/putcharstile.bas`); pending confirmation on real - hardware. -- `puttile.bas`: **did need a zx81sd override** — hardcoded screen/ - attribute base constants, same bug class as `print42.bas`/ - `print64.bas` (fixed the same way, self-modifying code patched at - runtime). Verified by simulation; pending confirmation on real - hardware. +- `putchars.bas`: **confirmed working on real hardware** — no ROM + dependency, no override needed + (`examples/sd81/putcharstile.bas`). +- `puttile.bas`: **confirmed working on real hardware** — needed a + zx81sd override for hardcoded screen/attribute base constants, same + bug class as `print42.bas`/`print64.bas` (fixed the same way, + self-modifying code patched at runtime). - `screen.bas`: **does depend on the ROM** (`$2538`/`$5C65`/`$19E8`, fixed Spectrum routines and sysvars to read back a screen character) — will need a real override, not just an audit. diff --git a/src/arch/zx81sd/doc/BASIC_CHANGES.md b/src/arch/zx81sd/doc/BASIC_CHANGES.md index 6a1053a7d..255cb592c 100644 --- a/src/arch/zx81sd/doc/BASIC_CHANGES.md +++ b/src/arch/zx81sd/doc/BASIC_CHANGES.md @@ -223,6 +223,7 @@ that shifted the drawing up before the check ran. Moving the final prompt to row 20 fixed it; `paint()`/`putTile()` were correct all along. Verified with breakpoints right after each function returns (not just at the end) to catch this class of false negative. +**Confirmed working on real hardware.** --- From 0fb1f2ae2229675000dc834b3169b95af3a634e8 Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 17:27:22 +0200 Subject: [PATCH 25/27] make tools executable --- src/arch/zx81sd/tools/split_sd81.py | 0 src/arch/zx81sd/tools/zx81_p_loader.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 src/arch/zx81sd/tools/split_sd81.py mode change 100644 => 100755 src/arch/zx81sd/tools/zx81_p_loader.py diff --git a/src/arch/zx81sd/tools/split_sd81.py b/src/arch/zx81sd/tools/split_sd81.py old mode 100644 new mode 100755 diff --git a/src/arch/zx81sd/tools/zx81_p_loader.py b/src/arch/zx81sd/tools/zx81_p_loader.py old mode 100644 new mode 100755 From ef8a727a38d7c312b7b5f5bc97e0b9cab08ae6af Mon Sep 17 00:00:00 2001 From: wilco2009 <43153593+wilco2009@users.noreply.github.com> Date: Sun, 5 Jul 2026 20:14:12 +0200 Subject: [PATCH 26/27] zx81sd: activate Chroma81 colour explicitly in stage 1 boot Found on real hardware: activating Superfast HiRes Spectrum mode (POKE 2045,172) does not turn on Chroma81 colour by itself -- screen came up monochrome despite correct INK/PAPER/attributes. EightyOne shows colour regardless, which is an emulator bug (needs reporting/ fixing there separately, not something to work around here). Fix: boot1.asm now also writes to the Chroma81 port ($7FEF) right after activating Spectrum mode: ld bc, $7FEF / ld a, 39 / out (c), a (bit5=1 color on, bit4=0 char-code mode) boot1.bin reassembled with zxbasm.py (34 bytes, was 27 -- exactly the 3 new instructions). Shared stage 1 loader used by every zx81sd program: no program needs recompiling, just copy the updated BOOT1.BIN to the SD card. Co-Authored-By: Claude Sonnet 5 --- src/arch/zx81sd/doc/MAP.md | 27 +++++++++++++++++++++++++++ src/arch/zx81sd/tools/boot1.asm | 25 ++++++++++++++++++++++--- src/arch/zx81sd/tools/boot1.bin | Bin 27 -> 34 bytes 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/arch/zx81sd/doc/MAP.md b/src/arch/zx81sd/doc/MAP.md index 758cb67b6..e9f2d7fef 100644 --- a/src/arch/zx81sd/doc/MAP.md +++ b/src/arch/zx81sd/doc/MAP.md @@ -531,3 +531,30 @@ doesn't unmap (the "release back to page 63" was an invention of this override, nothing needs it). Documented in the library's header: if a program uses block 7 for its own banking, it must remap its own page and call `SetBankPreservingINTs(7)` before using MSFS again. + +## Chroma81 colour mode not activated by Spectrum mode alone — RESOLVED (2026-07-05) + +Found on real hardware, not caught by the emulator: activating +Superfast HiRes Spectrum mode (`POKE 2045,172` / `ld ($07FD),a`) turns +on the Spectrum-compatible screen layout, but does **not** by itself +turn on Chroma81 colour on a real SD81 Booster — everything showed up +monochrome despite `INK`/`PAPER`/attributes being written correctly. +EightyOne shows colour regardless of this step, which is an **emulator +bug** (color should also require the same explicit activation to match +real hardware) — needs reporting/fixing in the EightyOne repository +separately, not something to work around here. + +Fix: `tools/boot1.asm` (stage 1 bootstrap) now also writes to the +Chroma81 port (`$7FEF`) right after activating Spectrum mode: + +``` +ld bc, $7FEF ; Chroma81 port: set known state +ld a, 39 ; bit5=1 color on, bit4=0 char-code mode +out (c), a +``` + +`tools/boot1.bin` reassembled with `zxbasm.py` (34 bytes, was 27 — +exactly the 3 new instructions' bytes: `01 EF 7F` / `3E 27` / `ED 79`). +This is the shared stage 1 loader used by every zx81sd program, so no +program needs recompiling — just copy the updated `BOOT1.BIN` to the +SD card. diff --git a/src/arch/zx81sd/tools/boot1.asm b/src/arch/zx81sd/tools/boot1.asm index 7eaa5830f..6ed9ea28c 100644 --- a/src/arch/zx81sd/tools/boot1.asm +++ b/src/arch/zx81sd/tools/boot1.asm @@ -9,15 +9,22 @@ ; 1. DI — disable interrupts (ZX81 FAST mode already does this, but ; it's repeated as a precaution before touching paging) ; 2. Activate Superfast HiRes Spectrum mode via the FPGA (POKE 2045 = $07FD) -; 3. Disable memory-mapped IO (POKE 2056 = $0808) -; 4. Map block 0 -> page 8 (OUT ($E7), page=8, block=0) +; 3. Activate Chroma81 colour mode (port $7FEF) -- required separately +; from step 2 on real hardware, see note below +; 4. Disable memory-mapped IO (POKE 2056 = $0808) +; 5. Map block 0 -> page 8 (OUT ($E7), page=8, block=0) ; Stage 2 ($0100-$0FFF on page 8) is now ready to run. -; 5. JP $0100 — jumps to stage 2 in the freshly mapped RAM +; 6. JP $0100 — jumps to stage 2 in the freshly mapped RAM ; ; Notes: ; - HFILE=$C000 is the FPGA's default value when activating mode 172. ; If your hardware requires setting it explicitly, uncomment the ; HFILE block at the end. +; - Chroma81 colour (step 3): activating Spectrum mode (step 2) does +; NOT turn color on by itself on real hardware -- confirmed on a +; real SD81 Booster (2026-07-05); the EightyOne emulator shows +; color regardless of this step, which is a known emulator bug +; (color should also require this OUT, to match real hardware). ; - Stage 1 does NOT initialize SP; stage 2 does (ld sp, $7FFF). ; - Blocks 1-5 are mapped in stage 2 (already running from page 8). ; @@ -55,6 +62,18 @@ SD81_STAGE1: ld a, 172 ld ($07FD), a + ; ------------------------------------------------------------------ + ; Activate Chroma81 colour mode + ; Activating Spectrum mode above does NOT turn color on by itself + ; on real hardware (confirmed on real SD81 Booster; the EightyOne + ; emulator shows color regardless, which is an emulator bug to + ; report/fix there). Chroma81 is a separate port ($7FEF) that must + ; be set explicitly. + ; ------------------------------------------------------------------ + ld bc, $7FEF ; Chroma81 port: set known state + ld a, 39 ; bit5=1 color on, bit4=0 char-code mode + out (c), a + ; ------------------------------------------------------------------ ; Disable memory-mapped IO ; POKE 2056, 0 -> ld ($0808), a diff --git a/src/arch/zx81sd/tools/boot1.bin b/src/arch/zx81sd/tools/boot1.bin index 0fc567529aa295b6cf20ef3bc636ee3ce75b37f6..bbaa78a56e9af8a24128a02e26780a1a98ad7115 100644 GIT binary patch literal 34 qcmeyY$Z$aEH@n>$qrdEo@9XW<-&U?S;^1K8uw&qR{$qrdFyjW{^iIP4hsp1-X;%)kf$jm8Oe From 6add187ea942ce08068e6817558badd330886244 Mon Sep 17 00:00:00 2001 From: wilco2009 Date: Sun, 5 Jul 2026 23:01:33 +0200 Subject: [PATCH 27/27] lint: format code --- .bumpversion.cfg | 36 +- .coveragerc | 6 +- .gitattributes | 36 +- .github/ISSUE_TEMPLATE/bug_report.yaml | 108 +- .github/ISSUE_TEMPLATE/bug_report_es.yaml | 108 +- .gitignore | 52 +- .pre-commit-config.yaml | 40 +- .readthedocs.yaml | 42 +- docs/imgs.sh | 30 +- docs/overrides/main.html | 10 +- docs/overrides/stylesheets/extra.css | 296 +-- poetry.lock | 2204 ++++++++--------- pyproject.toml | 374 +-- .../z80/peephole/opts/000_o1_push_pop.opt | 28 +- .../z80/peephole/opts/000_o3_push_pop.opt | 28 +- .../peephole/opts/001_o1_ld_XXYY_ldYYXX.opt | 36 +- .../peephole/opts/002_o1_push_hlde_to_ex.opt | 50 +- .../z80/peephole/opts/003_o1_useless_jp.opt | 34 +- .../peephole/opts/004_o1_push_pop_ldld.opt | 54 +- .../peephole/opts/005_o1_push_af_ld_aX.opt | 46 +- .../peephole/opts/006_o1_push_af_pop_XX.opt | 54 +- .../peephole/opts/006_o3_push_af_pop_XX.opt | 54 +- .../z80/peephole/opts/007_o1_ex_de_hl.opt | 32 +- src/arch/z80/peephole/opts/008_o1_sbc_jp.opt | 56 +- src/arch/z80/peephole/opts/009_o1_inc_mem.opt | 60 +- .../opts/010_o1_ld_de_hl_ex_de_hl.opt | 66 +- .../opts/011_o1_ld_h_a_pop_af_or_h.opt | 54 +- .../opts/012_o1_ld_hl_push_pop_de.opt | 54 +- src/arch/z80/peephole/opts/013_o1_neg_neg.opt | 32 +- .../z80/peephole/opts/014_o1_or_sbc_a_a.opt | 44 +- .../opts/015_o3_ld_r_n_not_required.opt | 36 +- .../z80/peephole/opts/016_o1_xor_a_jp_z.opt | 44 +- .../peephole/opts/017_jp_cond_jp_labell.opt | 60 +- .../z80/peephole/opts/018_EQ16_by_sbchl.opt | 66 +- .../peephole/opts/019_sub1_jpnc_ora_jpz.opt | 56 +- .../peephole/opts/020_o1_bool_norm_empty.opt | 64 +- .../peephole/opts/021_o1_bool_norm_neg.opt | 52 +- .../z80/peephole/opts/022_insr_a_or_a.opt | 48 +- .../z80/peephole/opts/023_ld_hl_bc_outl.opt | 54 +- .../opts/024_o1_bool_norm_duble_neg.opt | 46 +- .../z80/peephole/opts/025_ld_h_a_oper_A.opt | 54 +- .../z80/peephole/opts/027_ld_h_a_oper_A.opt | 44 +- src/arch/z80/peephole/opts/028_o2_pop_up.opt | 56 +- src/arch/z80/peephole/opts/028_o3_pop_up.opt | 54 +- src/arch/z80/peephole/opts/029_cp_0_or_a.opt | 40 +- .../z80/peephole/opts/030_ora_jp_nc_jp.opt | 44 +- src/arch/z80/peephole/opts/031_jpX_Y_jpX.opt | 50 +- .../peephole/opts/032_call_LOADSTR_ld_a1.opt | 44 +- .../peephole/opts/050_o1_ld_a_ld_h_pop.opt | 58 +- .../peephole/opts/051_o1_ld_a_ld_h_a_pop.opt | 58 +- .../z80/peephole/opts/052_o3_ldhl_ld_a_l.opt | 44 +- src/arch/z80/peephole/opts/053_o1_ldh_nnn.opt | 42 +- .../peephole/opts/054_o3_ld_a_N_ld_hl_a.opt | 48 +- .../peephole/opts/100_o3_ld_rr_N_ld_ss_N.opt | 40 +- .../opts/101_o3_ld_rr_N_ld_h_r_ld_l_r.opt | 72 +- .../peephole/opts/102_o3_ld_r_n_ld_r_n2.opt | 36 +- .../z80/peephole/opts/103_o3_or_and_a.opt | 34 +- src/arch/z80/peephole/opts/103_o3_xor_a.opt | 30 +- src/arch/z80/peephole/opts/104_o3_opt27.opt | 58 +- src/arch/z80/peephole/opts/105_o3_opt27.opt | 58 +- src/arch/z80/peephole/opts/106_o3_exdehl.opt | 46 +- .../z80/peephole/opts/107_o3_exdehl_ldde.opt | 50 +- src/arch/z80/peephole/opts/108_o3_inc_dec.opt | 56 +- .../peephole/opts/109_o4_ld_mem_op_ld_mem.opt | 34 +- .../peephole/opts/110_o4_ld_mem_op_ld_mem.opt | 38 +- .../zxnext/peephole/opts/060_o1_mul_de.opt | 50 +- src/lib/arch/zx48k/runtime/spectranet.inc | 286 +-- src/lib/arch/zx48k/stdlib/README | 8 +- src/lib/arch/zxnext/runtime/spectranet.inc | 286 +-- src/lib/arch/zxnext/stdlib/README | 8 +- src/parsetab/tabs.dbm.bak | 8 +- src/parsetab/tabs.dbm.dir | 8 +- src/zxbpp/zxbpp.py | 4 +- tests/functional/Makefile | 82 +- tests/functional/arch/zx48k/border00_IC.ic | 8 +- tests/functional/arch/zx48k/func_call_IC.ic | 34 +- tests/functional/zxbpp/builtin.out | 56 +- tests/functional/zxbpp/emook0.out | 18 +- tests/functional/zxbpp/iflogic.out | 36 +- tests/functional/zxbpp/init_dot.out | 22 +- tests/functional/zxbpp/line_asm.out | 8 +- tests/functional/zxbpp/once.out | 16 +- tests/functional/zxbpp/once_base.out | 24 +- tests/functional/zxbpp/other_arch.out | 510 ++-- tests/functional/zxbpp/prepro00.out | 256 +- tests/functional/zxbpp/prepro01.out | 258 +- tests/functional/zxbpp/prepro02.out | 16 +- tests/functional/zxbpp/prepro03.out | 18 +- tests/functional/zxbpp/prepro04.out | 12 +- tests/functional/zxbpp/prepro05.out | 14 +- tests/functional/zxbpp/prepro06.out | 12 +- tests/functional/zxbpp/prepro09.out | 8 +- tests/functional/zxbpp/prepro10.out | 12 +- tests/functional/zxbpp/prepro11.out | 16 +- tests/functional/zxbpp/prepro12.out | 16 +- tests/functional/zxbpp/prepro13.out | 16 +- tests/functional/zxbpp/prepro14.out | 18 +- tests/functional/zxbpp/prepro15.out | 24 +- tests/functional/zxbpp/prepro16.out | 22 +- tests/functional/zxbpp/prepro17.out | 16 +- tests/functional/zxbpp/prepro18.out | 14 +- tests/functional/zxbpp/prepro19.out | 14 +- tests/functional/zxbpp/prepro20.out | 14 +- tests/functional/zxbpp/prepro21.out | 16 +- tests/functional/zxbpp/prepro23.out | 18 +- tests/functional/zxbpp/prepro24.out | 22 +- tests/functional/zxbpp/prepro25.out | 14 +- tests/functional/zxbpp/prepro26.out | 14 +- tests/functional/zxbpp/prepro27.out | 28 +- tests/functional/zxbpp/prepro29.out | 8 +- tests/functional/zxbpp/prepro30.out | 256 +- tests/functional/zxbpp/prepro31.out | 8 +- tests/functional/zxbpp/prepro32.out | 28 +- tests/functional/zxbpp/prepro33.out | 70 +- tests/functional/zxbpp/prepro34.out | 10 +- tests/functional/zxbpp/prepro36.out | 8 +- tests/functional/zxbpp/prepro37.out | 12 +- tests/functional/zxbpp/prepro38.out | 14 +- tests/functional/zxbpp/prepro39.out | 18 +- tests/functional/zxbpp/prepro40.out | 8 +- tests/functional/zxbpp/prepro41.out | 16 +- tests/functional/zxbpp/prepro42.out | 8 +- tests/functional/zxbpp/prepro43.out | 12 +- tests/functional/zxbpp/prepro44.out | 8 +- tests/functional/zxbpp/prepro45.out | 8 +- tests/functional/zxbpp/prepro46.out | 12 +- tests/functional/zxbpp/prepro47.out | 10 +- tests/functional/zxbpp/prepro48.out | 10 +- tests/functional/zxbpp/prepro49.out | 10 +- tests/functional/zxbpp/prepro50.out | 16 +- tests/functional/zxbpp/prepro51.out | 8 +- tests/functional/zxbpp/prepro52.out | 10 +- tests/functional/zxbpp/prepro53.out | 10 +- tests/functional/zxbpp/prepro54.out | 10 +- tests/functional/zxbpp/prepro55.out | 14 +- tests/functional/zxbpp/prepro56.out | 10 +- tests/functional/zxbpp/prepro57.out | 10 +- tests/functional/zxbpp/prepro58.out | 12 +- tests/functional/zxbpp/prepro59.out | 10 +- tests/functional/zxbpp/prepro60.out | 12 +- tests/functional/zxbpp/prepro61.out | 12 +- tests/functional/zxbpp/prepro62.out | 14 +- tests/functional/zxbpp/prepro63.out | 12 +- tests/functional/zxbpp/prepro64.out | 100 +- tests/functional/zxbpp/prepro70.out | 26 +- tests/functional/zxbpp/prepro71.out | 486 ++-- tests/functional/zxbpp/prepro72.out | 26 +- tests/functional/zxbpp/prepro73.out | 134 +- tests/functional/zxbpp/prepro74.out | 120 +- tests/functional/zxbpp/prepro75.out | 18 +- tests/functional/zxbpp/prepro77.out | 4 +- tests/functional/zxbpp/prepro80.out | 40 +- tests/functional/zxbpp/prepro81.out | 8 +- tests/functional/zxbpp/prepro82.out | 14 +- tests/functional/zxbpp/prepro85.out | 264 +- tests/functional/zxbpp/prepro90.out | 14 +- tests/functional/zxbpp/prepro91.out | 10 +- tests/functional/zxbpp/prepro92.out | 14 +- tests/functional/zxbpp/prepro93.out | 14 +- tests/functional/zxbpp/prepro94.out | 12 +- tests/functional/zxbpp/prepro95.out | 10 +- tests/functional/zxbpp/spectrum.out | 10 +- tests/functional/zxbpp/stringizing0.out | 12 +- tests/functional/zxbpp/stringizing1.out | 16 +- tests/functional/zxbpp/stringizing2.out | 12 +- tests/functional/zxbpp/token-paste0.out | 18 +- tests/functional/zxbpp/token-paste1.out | 16 +- tests/runtime/Makefile | 14 +- tests/runtime/run | 18 +- tests/runtime/test_all | 8 +- tests/runtime/test_case | 34 +- tests/runtime/update_test.sh | 14 +- tools/profile.sh | 8 +- 173 files changed, 5192 insertions(+), 5194 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 9da15245e..8ad31de00 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,18 +1,18 @@ -[bumpversion] -current_version = 1.18.7 - -[bumpversion:file:src/zxbc/version.py] -search = VERSION: Final[str] = "{current_version}" -replace = VERSION: Final[str] = "{new_version}" - -[bumpversion:file:src/zxbasm/version.py] -search = VERSION = "{current_version}" -replace = VERSION = "{new_version}" - -[bumpversion:file:pyproject.toml] -search = version = "{current_version}" -replace = version = "{new_version}" - -[bumpversion:file:docs/archive.md] -search = {current_version} -replace = {new_version} +[bumpversion] +current_version = 1.18.7 + +[bumpversion:file:src/zxbc/version.py] +search = VERSION: Final[str] = "{current_version}" +replace = VERSION: Final[str] = "{new_version}" + +[bumpversion:file:src/zxbasm/version.py] +search = VERSION = "{current_version}" +replace = VERSION = "{new_version}" + +[bumpversion:file:pyproject.toml] +search = version = "{current_version}" +replace = version = "{new_version}" + +[bumpversion:file:docs/archive.md] +search = {current_version} +replace = {new_version} diff --git a/.coveragerc b/.coveragerc index f3a5cd4ea..2301d8835 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,3 +1,3 @@ -[run] -omit = - .tox/* +[run] +omit = + .tox/* diff --git a/.gitattributes b/.gitattributes index f4802a3df..335e83d39 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,18 +1,18 @@ - -* text=false - -*.bas -crlf -*.asm -crlf -*.bi -crlf - -*.txt text -*.md text -*.py text -*.ini text -*.yml text - -*.png binary -*.bin binary - -*.bas linguist-vendored -*.asm linguist-vendored + +* text=false + +*.bas -crlf +*.asm -crlf +*.bi -crlf + +*.txt text +*.md text +*.py text +*.ini text +*.yml text + +*.png binary +*.bin binary + +*.bas linguist-vendored +*.asm linguist-vendored diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 2e337207c..2d1da814d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -1,54 +1,54 @@ -name: Bug Report -description: File a bug report. -title: "[Bug]: " -labels: ["bug", "triage"] -projects: ["boriel-basic/1"] -assignees: - - boriel -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this bug report! - - type: input - id: contact - attributes: - label: Contact Details - description: How can we get in touch with you if we need more info? - placeholder: e.g. Telegram @nickname - validations: - required: false - - type: input - id: version - attributes: - label: Compiler version - description: | - Always check you have the latest version. - You can get the version with zxbc --version - placeholder: v1.17.3 - validations: - required: true - - type: textarea - id: what-happened - attributes: - label: What happened? - description: Also tell us, what did you expect to happen? - placeholder: Tell us what you see! - validations: - required: true - - type: textarea - id: logs - attributes: - label: Error and Warning messages - description: | - Please copy and paste any relevant log output. - This will be automatically formatted into code, so no need for backticks. - render: shell - - type: checkboxes - id: terms - attributes: - label: Code of Conduct - description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/boriel-basic/zxbasic/blob/main/CODE_OF_CONDUCT.md). - options: - - label: I agree to follow this project's Code of Conduct - required: true +name: Bug Report +description: File a bug report. +title: "[Bug]: " +labels: ["bug", "triage"] +projects: ["boriel-basic/1"] +assignees: + - boriel +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: input + id: contact + attributes: + label: Contact Details + description: How can we get in touch with you if we need more info? + placeholder: e.g. Telegram @nickname + validations: + required: false + - type: input + id: version + attributes: + label: Compiler version + description: | + Always check you have the latest version. + You can get the version with zxbc --version + placeholder: v1.17.3 + validations: + required: true + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Also tell us, what did you expect to happen? + placeholder: Tell us what you see! + validations: + required: true + - type: textarea + id: logs + attributes: + label: Error and Warning messages + description: | + Please copy and paste any relevant log output. + This will be automatically formatted into code, so no need for backticks. + render: shell + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/boriel-basic/zxbasic/blob/main/CODE_OF_CONDUCT.md). + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/ISSUE_TEMPLATE/bug_report_es.yaml b/.github/ISSUE_TEMPLATE/bug_report_es.yaml index d68e4986c..c834d2218 100644 --- a/.github/ISSUE_TEMPLATE/bug_report_es.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report_es.yaml @@ -1,54 +1,54 @@ -name: Informe de fallo -description: Informar de un problema -title: "[Bug]: " -labels: ["bug", "triage"] -projects: ["boriel-basic/1"] -assignees: - - boriel -body: - - type: markdown - attributes: - value: | - ¡Gracias por tomarte el tiempo de informar de este fallo! - - type: input - id: contact - attributes: - label: Detalles de Contacto - description: ¿Cómo podemos contactarte si ncesitamos información? (opcional) - placeholder: e.g. Telegram @nickname - validations: - required: false - - type: input - id: version - attributes: - label: Versión del compilador - description: | - Always check you have the latest version. - You can get the version with zxbc --version - placeholder: v1.17.3 - validations: - required: true - - type: textarea - id: what-happened - attributes: - label: ¿Qué ha pasado? - description: Cuéntanos también que esperabas que sucediera. - placeholder: (descripción del problema) - validations: - required: true - - type: textarea - id: logs - attributes: - label: Mensajes de error o warnings del compilador - description: | - Por favor, copia y pega la salida de los logs. - Serán formateados automáticamente como salida de log. No necesitas usar formato. - render: shell - - type: checkboxes - id: terms - attributes: - label: Código de Conducta - description: Al enviar este formulario, acuerdas seguir nuestro [Código de Conducta](https://github.com/boriel-basic/zxbasic/blob/main/CODE_OF_CONDUCT.md). - options: - - label: I agree to follow this project's Code of Conduct - required: true +name: Informe de fallo +description: Informar de un problema +title: "[Bug]: " +labels: ["bug", "triage"] +projects: ["boriel-basic/1"] +assignees: + - boriel +body: + - type: markdown + attributes: + value: | + ¡Gracias por tomarte el tiempo de informar de este fallo! + - type: input + id: contact + attributes: + label: Detalles de Contacto + description: ¿Cómo podemos contactarte si ncesitamos información? (opcional) + placeholder: e.g. Telegram @nickname + validations: + required: false + - type: input + id: version + attributes: + label: Versión del compilador + description: | + Always check you have the latest version. + You can get the version with zxbc --version + placeholder: v1.17.3 + validations: + required: true + - type: textarea + id: what-happened + attributes: + label: ¿Qué ha pasado? + description: Cuéntanos también que esperabas que sucediera. + placeholder: (descripción del problema) + validations: + required: true + - type: textarea + id: logs + attributes: + label: Mensajes de error o warnings del compilador + description: | + Por favor, copia y pega la salida de los logs. + Serán formateados automáticamente como salida de log. No necesitas usar formato. + render: shell + - type: checkboxes + id: terms + attributes: + label: Código de Conducta + description: Al enviar este formulario, acuerdas seguir nuestro [Código de Conducta](https://github.com/boriel-basic/zxbasic/blob/main/CODE_OF_CONDUCT.md). + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.gitignore b/.gitignore index 1ca58c0ee..3282193d6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,26 @@ -# Created by .ignore support plugin (hsz.mobi) -*.egg-info/ -*.pyc -.cache/ -.pytest_cache/ -.mypy_cache/ -.pytype/ -.idea/ -.python-version -.tox/ -MANIFEST -dist/ -examples/*.bin -examples/*.tzx -scratch/ -.coverage -htmlcov/ -build/ -venv/ -.pypi-token -.coverage.* - -# zx81sd test build artifacts (compila.bat + split_sd81.py output) -/compila.bat -/TESTSD81*.P -/TESTSD81*.BIN +# Created by .ignore support plugin (hsz.mobi) +*.egg-info/ +*.pyc +.cache/ +.pytest_cache/ +.mypy_cache/ +.pytype/ +.idea/ +.python-version +.tox/ +MANIFEST +dist/ +examples/*.bin +examples/*.tzx +scratch/ +.coverage +htmlcov/ +build/ +venv/ +.pypi-token +.coverage.* + +# zx81sd test build artifacts (compila.bat + split_sd81.py output) +/compila.bat +/TESTSD81*.P +/TESTSD81*.BIN diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d69ca1233..e55fcbc4c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,20 +1,20 @@ -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 - hooks: - - id: check-yaml - exclude: \.(bin|out)$ - - id: end-of-file-fixer - exclude: \.(bin|out)$ - - id: trailing-whitespace - exclude: \.(bin|out)$ - - - repo: https://github.com/astral-sh/ruff-pre-commit - # Ruff version. - rev: v0.15.12 - hooks: - # Run the linter. - - id: ruff - args: [ --fix ] - # Run the formatter. - - id: ruff-format +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-yaml + exclude: \.(bin|out)$ + - id: end-of-file-fixer + exclude: \.(bin|out)$ + - id: trailing-whitespace + exclude: \.(bin|out)$ + + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.15.12 + hooks: + # Run the linter. + - id: ruff + args: [ --fix ] + # Run the formatter. + - id: ruff-format diff --git a/.readthedocs.yaml b/.readthedocs.yaml index afe9f1939..ae98c99f5 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,21 +1,21 @@ -# .readthedocs.yaml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Set the version of Python and other tools you might need -build: - os: ubuntu-22.04 - tools: - python: "3.11" - -python: - install: - - requirements: ./docs/requirements.txt - -# Build documentation in the docs/ directory with mkdocs -mkdocs: - configuration: mkdocs.yml - fail_on_warning: false +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +python: + install: + - requirements: ./docs/requirements.txt + +# Build documentation in the docs/ directory with mkdocs +mkdocs: + configuration: mkdocs.yml + fail_on_warning: false diff --git a/docs/imgs.sh b/docs/imgs.sh index c3fc30811..9531161b6 100755 --- a/docs/imgs.sh +++ b/docs/imgs.sh @@ -1,15 +1,15 @@ -#!/bin/bash - -for i in $(egrep -e 'png|gif' released_programs.md); do - IMG=$(echo $i|sed -e 's/^!*\[\([^]]*\)\].*$/\1/') - LIMG=$(echo $IMG| tr A-Z a-z) - DEST=./img/games/$LIMG - (ls $DEST 2>/dev/null) && continue - IMGPATH=$(find ~/migration/client6/web10/web/wiki/ -type f -name "240px-$IMG") - if [[ "$IMGPATH" == "" ]]; then - continue - fi - #echo "Image: $IMG => $IMGPATH" - cp -v $IMGPATH $DEST -done - +#!/bin/bash + +for i in $(egrep -e 'png|gif' released_programs.md); do + IMG=$(echo $i|sed -e 's/^!*\[\([^]]*\)\].*$/\1/') + LIMG=$(echo $IMG| tr A-Z a-z) + DEST=./img/games/$LIMG + (ls $DEST 2>/dev/null) && continue + IMGPATH=$(find ~/migration/client6/web10/web/wiki/ -type f -name "240px-$IMG") + if [[ "$IMGPATH" == "" ]]; then + continue + fi + #echo "Image: $IMG => $IMGPATH" + cp -v $IMGPATH $DEST +done + diff --git a/docs/overrides/main.html b/docs/overrides/main.html index 86d46c0a3..c3e7f2261 100644 --- a/docs/overrides/main.html +++ b/docs/overrides/main.html @@ -1,6 +1,6 @@ -{% extends "base.html" %} - -{% block extrahead %} - - +{% extends "base.html" %} + +{% block extrahead %} + + {% endblock %} \ No newline at end of file diff --git a/docs/overrides/stylesheets/extra.css b/docs/overrides/stylesheets/extra.css index 3a81f3722..89ae1f4b5 100644 --- a/docs/overrides/stylesheets/extra.css +++ b/docs/overrides/stylesheets/extra.css @@ -1,148 +1,148 @@ -/* Light mode - very light yellow header */ -[data-md-color-scheme="default"] .md-header { - background-color: #d0d6da; /* Very light yellow background color */ - height: 4rem; -} - -[data-md-color-scheme="default"] .md-header__title { - color: #23231c; - padding-left: 2rem; -} - -/* Dark mode - dark gray header */ -[data-md-color-scheme="slate"] .md-header { - background-color: #333333; /* Dark gray background color */ - /*background-image: url('../img/boriel-basic-sinclair.png');*/ - /*background-repeat: no-repeat;*/ - /*background-position: center center;*/ - /*background-size: auto 90%;*/ - height: 4rem; -} - -[data-md-color-scheme="slate"] .md-header__title { - color: #af0d0d; - padding-left: 2rem; -} - -.md-header__title { - font-family: 'Press Start 2P', cursive; - } - -/* Make the site name responsive */ -.md-header__title .md-header__ellipsis { - font-size: 1.2rem; -} - -/* Responsive adjustments for smaller screens */ -@media screen and (max-width: 1220px) { - [data-md-color-scheme="default"] .md-header { - height: 3.5rem; - } - - [data-md-color-scheme="slate"] .md-header { - height: 3.5rem; - } - - [data-md-color-scheme="default"] .md-header__title { - padding-left: 3.0rem; - } - - [data-md-color-scheme="slate"] .md-header__title { - padding-left: 3.0rem; - } - - .md-header__title .md-header__ellipsis { - font-size: 1rem; - } - - .md-header__inner { - display: flex; - } - - .md-header__button { - margin: 0; - padding: 0.5rem; - } - - .md-header__button[for="__drawer"] { - order: 3; - } - - label[for="__search"] { - order: 2; - } -} - -.md-header__inner { - background-image: url('../img/zxbasic_logo.png'); - background-repeat: no-repeat; - background-position: left center; - background-size: auto 100%; - height: 100%; -} - -@media screen and (max-width: 480px) { - [data-md-color-scheme="default"] .md-header { - height: 3rem; - } - - [data-md-color-scheme="slate"] .md-header { - height: 3rem; - } - - [data-md-color-scheme="default"] .md-header__title { - padding-left: 48px; - } - - [data-md-color-scheme="slate"] .md-header__title { - padding-left: 48px; - } - - .md-header__title .md-header__ellipsis { - font-size: 0.8rem; - } - - .md-header__inner { - display: flex; - } - - .md-header__button { - margin: 2px; - padding: 2px; - } - - .md-header__button[for="__drawer"] { - order: 3; /* Ponlo al final (tras tema y búsqueda) */ - } - - label[for="__search"] { - order: 2; - } -} - -/*!* Make the logo size 100% of the header *!*/ -.md-header__button.md-logo { - color: #ff000000; - height: 100%; -} - -/* Increase font weight for headings */ -h1, h2, h3, h4, h5, h6 { - /* font-weight: 600; /* Bold */ - font-family: "Roboto Mono", sans-serif; -} - -/* Target Material theme specific heading classes if any */ -.md-typeset h1, -.md-typeset h2, -.md-typeset h3, -.md-typeset h4, -.md-typeset h5, -.md-typeset h6 { - font-weight: 600; /* Bold */ -} - -.md-typeset .admonition, -.md-typeset details { - font-size: 0.75rem -} +/* Light mode - very light yellow header */ +[data-md-color-scheme="default"] .md-header { + background-color: #d0d6da; /* Very light yellow background color */ + height: 4rem; +} + +[data-md-color-scheme="default"] .md-header__title { + color: #23231c; + padding-left: 2rem; +} + +/* Dark mode - dark gray header */ +[data-md-color-scheme="slate"] .md-header { + background-color: #333333; /* Dark gray background color */ + /*background-image: url('../img/boriel-basic-sinclair.png');*/ + /*background-repeat: no-repeat;*/ + /*background-position: center center;*/ + /*background-size: auto 90%;*/ + height: 4rem; +} + +[data-md-color-scheme="slate"] .md-header__title { + color: #af0d0d; + padding-left: 2rem; +} + +.md-header__title { + font-family: 'Press Start 2P', cursive; + } + +/* Make the site name responsive */ +.md-header__title .md-header__ellipsis { + font-size: 1.2rem; +} + +/* Responsive adjustments for smaller screens */ +@media screen and (max-width: 1220px) { + [data-md-color-scheme="default"] .md-header { + height: 3.5rem; + } + + [data-md-color-scheme="slate"] .md-header { + height: 3.5rem; + } + + [data-md-color-scheme="default"] .md-header__title { + padding-left: 3.0rem; + } + + [data-md-color-scheme="slate"] .md-header__title { + padding-left: 3.0rem; + } + + .md-header__title .md-header__ellipsis { + font-size: 1rem; + } + + .md-header__inner { + display: flex; + } + + .md-header__button { + margin: 0; + padding: 0.5rem; + } + + .md-header__button[for="__drawer"] { + order: 3; + } + + label[for="__search"] { + order: 2; + } +} + +.md-header__inner { + background-image: url('../img/zxbasic_logo.png'); + background-repeat: no-repeat; + background-position: left center; + background-size: auto 100%; + height: 100%; +} + +@media screen and (max-width: 480px) { + [data-md-color-scheme="default"] .md-header { + height: 3rem; + } + + [data-md-color-scheme="slate"] .md-header { + height: 3rem; + } + + [data-md-color-scheme="default"] .md-header__title { + padding-left: 48px; + } + + [data-md-color-scheme="slate"] .md-header__title { + padding-left: 48px; + } + + .md-header__title .md-header__ellipsis { + font-size: 0.8rem; + } + + .md-header__inner { + display: flex; + } + + .md-header__button { + margin: 2px; + padding: 2px; + } + + .md-header__button[for="__drawer"] { + order: 3; /* Ponlo al final (tras tema y búsqueda) */ + } + + label[for="__search"] { + order: 2; + } +} + +/*!* Make the logo size 100% of the header *!*/ +.md-header__button.md-logo { + color: #ff000000; + height: 100%; +} + +/* Increase font weight for headings */ +h1, h2, h3, h4, h5, h6 { + /* font-weight: 600; /* Bold */ + font-family: "Roboto Mono", sans-serif; +} + +/* Target Material theme specific heading classes if any */ +.md-typeset h1, +.md-typeset h2, +.md-typeset h3, +.md-typeset h4, +.md-typeset h5, +.md-typeset h6 { + font-weight: 600; /* Bold */ +} + +.md-typeset .admonition, +.md-typeset details { + font-size: 0.75rem +} diff --git a/poetry.lock b/poetry.lock index ca40be381..455d95296 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,1102 +1,1102 @@ -# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. - -[[package]] -name = "bump2version" -version = "1.0.1" -description = "Version-bump your software with a single command!" -optional = false -python-versions = ">=3.5" -groups = ["dev"] -files = [ - {file = "bump2version-1.0.1-py2.py3-none-any.whl", hash = "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410"}, - {file = "bump2version-1.0.1.tar.gz", hash = "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6"}, -] - -[[package]] -name = "cfgv" -version = "3.5.0" -description = "Validate configuration and produce human readable error messages." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0"}, - {file = "cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132"}, -] - -[[package]] -name = "click" -version = "8.3.3" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613"}, - {file = "click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "coverage" -version = "7.13.5" -description = "Code coverage measurement for Python" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "coverage-7.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0723d2c96324561b9aa76fb982406e11d93cdb388a7a7da2b16e04719cf7ca5"}, - {file = "coverage-7.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52f444e86475992506b32d4e5ca55c24fc88d73bcbda0e9745095b28ef4dc0cf"}, - {file = "coverage-7.13.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:704de6328e3d612a8f6c07000a878ff38181ec3263d5a11da1db294fa6a9bdf8"}, - {file = "coverage-7.13.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1a6d79a14e1ec1832cabc833898636ad5f3754a678ef8bb4908515208bf84f4"}, - {file = "coverage-7.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79060214983769c7ba3f0cee10b54c97609dca4d478fa1aa32b914480fd5738d"}, - {file = "coverage-7.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:356e76b46783a98c2a2fe81ec79df4883a1e62895ea952968fb253c114e7f930"}, - {file = "coverage-7.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cef0cdec915d11254a7f549c1170afecce708d30610c6abdded1f74e581666d"}, - {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc022073d063b25a402454e5712ef9e007113e3a676b96c5f29b2bda29352f40"}, - {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b74db26dfea4f4e50d48a4602207cd1e78be33182bc9cbf22da94f332f99878"}, - {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad146744ca4fd09b50c482650e3c1b1f4dfa1d4792e0a04a369c7f23336f0400"}, - {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c555b48be1853fe3997c11c4bd521cdd9a9612352de01fa4508f16ec341e6fe0"}, - {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7034b5c56a58ae5e85f23949d52c14aca2cfc6848a31764995b7de88f13a1ea0"}, - {file = "coverage-7.13.5-cp310-cp310-win32.whl", hash = "sha256:eb7fdf1ef130660e7415e0253a01a7d5a88c9c4d158bcf75cbbd922fd65a5b58"}, - {file = "coverage-7.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:3e1bb5f6c78feeb1be3475789b14a0f0a5b47d505bfc7267126ccbd50289999e"}, - {file = "coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d"}, - {file = "coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587"}, - {file = "coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642"}, - {file = "coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b"}, - {file = "coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686"}, - {file = "coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743"}, - {file = "coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75"}, - {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209"}, - {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a"}, - {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e"}, - {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd"}, - {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8"}, - {file = "coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf"}, - {file = "coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9"}, - {file = "coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028"}, - {file = "coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01"}, - {file = "coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422"}, - {file = "coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f"}, - {file = "coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5"}, - {file = "coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376"}, - {file = "coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256"}, - {file = "coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c"}, - {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5"}, - {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09"}, - {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9"}, - {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf"}, - {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c"}, - {file = "coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf"}, - {file = "coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810"}, - {file = "coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de"}, - {file = "coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1"}, - {file = "coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3"}, - {file = "coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26"}, - {file = "coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3"}, - {file = "coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b"}, - {file = "coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a"}, - {file = "coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969"}, - {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161"}, - {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15"}, - {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1"}, - {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6"}, - {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17"}, - {file = "coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85"}, - {file = "coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b"}, - {file = "coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664"}, - {file = "coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d"}, - {file = "coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0"}, - {file = "coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806"}, - {file = "coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3"}, - {file = "coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9"}, - {file = "coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd"}, - {file = "coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606"}, - {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e"}, - {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0"}, - {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87"}, - {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479"}, - {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2"}, - {file = "coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a"}, - {file = "coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819"}, - {file = "coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911"}, - {file = "coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f"}, - {file = "coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e"}, - {file = "coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a"}, - {file = "coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510"}, - {file = "coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247"}, - {file = "coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6"}, - {file = "coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0"}, - {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882"}, - {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740"}, - {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16"}, - {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0"}, - {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0"}, - {file = "coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc"}, - {file = "coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633"}, - {file = "coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8"}, - {file = "coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b"}, - {file = "coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c"}, - {file = "coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9"}, - {file = "coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29"}, - {file = "coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607"}, - {file = "coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90"}, - {file = "coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3"}, - {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab"}, - {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562"}, - {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2"}, - {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea"}, - {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a"}, - {file = "coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215"}, - {file = "coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43"}, - {file = "coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45"}, - {file = "coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61"}, - {file = "coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179"}, -] - -[package.extras] -toml = ["tomli ; python_full_version <= \"3.11.0a6\""] - -[[package]] -name = "distlib" -version = "0.4.0" -description = "Distribution utilities" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, - {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, -] - -[[package]] -name = "execnet" -version = "2.1.2" -description = "execnet: rapid multi-Python deployment" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, - {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, -] - -[package.extras] -testing = ["hatch", "pre-commit", "pytest", "tox"] - -[[package]] -name = "filelock" -version = "3.29.0" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258"}, - {file = "filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90"}, -] - -[[package]] -name = "ghp-import" -version = "2.1.0" -description = "Copy your docs directly to the gh-pages branch." -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, - {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, -] - -[package.dependencies] -python-dateutil = ">=2.8.1" - -[package.extras] -dev = ["flake8", "markdown", "twine", "wheel"] - -[[package]] -name = "identify" -version = "2.6.19" -description = "File identification library for Python" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a"}, - {file = "identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842"}, -] - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "iniconfig" -version = "2.3.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, - {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, -] - -[[package]] -name = "jinja2" -version = "3.1.6" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, - {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "librt" -version = "0.10.0" -description = "Mypyc runtime library" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -markers = "platform_python_implementation != \"PyPy\"" -files = [ - {file = "librt-0.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dc99f9642100b86e5f6bb14cdc9970009e31a9ef7d64df6704b7018451524a3"}, - {file = "librt-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8298cedfcfaff3790000bd057aaaa3df1b0ab54cf7b48eeab16184cbb1bc66b9"}, - {file = "librt-0.10.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7dbe312dbf76468255b79a7ba311236fde620f2f7055fc09d421e31340314e"}, - {file = "librt-0.10.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:56ed90c48c19249012dadfd79a1bc13bd5168ea60a70722d330a3a600c0b1852"}, - {file = "librt-0.10.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d74ca0f4b2b09c117f913d4df01f6b934dff8a271096b35167d5264a31649f0"}, - {file = "librt-0.10.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8eb2daa9375f93c0e55ff5e44a4bbe98f39e5fe52e1abf9c97acb67743b61bf8"}, - {file = "librt-0.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7b09b90e634e6dff57978cd358070046071e2b120501f10787aeb35425f504f6"}, - {file = "librt-0.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2cf22fd379d60c739b800d4295ed34045f8b04aa8df9c12bd2f8f43f7fe672b7"}, - {file = "librt-0.10.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:74c798793fcf29a84d442278ebe0bb1fff79fe58ac4106eeff7019cbba861423"}, - {file = "librt-0.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dc4f1573401e8dbe6c26511fe027620b0fb30ae9a7ab814e02e510626b8b5f9c"}, - {file = "librt-0.10.0-cp310-cp310-win32.whl", hash = "sha256:e1428275f5fe3d4db6822e58d8b005a5b28ffca55e8433ebc051247fbe46429f"}, - {file = "librt-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:0708e9408f585b0f065081680583a577652099680ccf820c7538904322b679c3"}, - {file = "librt-0.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:01b4500ca3a625450c032a9142a8e843923ce263fa8a92ad1b38927cabe2fe72"}, - {file = "librt-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b7e42d1b3e300d20bfc87e72ffd62f0a92a2cb3c35f7bf90df90c9d2a49f74c"}, - {file = "librt-0.10.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8ef7b8c61ce3a1b597cd3e15348ff1574325165c2e7ce09a718154cde2a7950"}, - {file = "librt-0.10.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:e73c84f72d1fa0d6eaa7a1930b436ba8d2c90c58d77bfabb09995a69ad35f6c0"}, - {file = "librt-0.10.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9728cb98713bd862fb8f4fd6a642d1896c86058a41d77c70f3d5cee75e725275"}, - {file = "librt-0.10.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:648b7e941d20acd72f9652115e0e53facd98156d61f9ebf7a812bdef8bdccea9"}, - {file = "librt-0.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c3e33747c068e86a9007c20fdb777eb5ba8d3d19136d7812f88e69a713041b6f"}, - {file = "librt-0.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d509c745bf7e77d1107cf05e6abb249dc03fad13eb39f2286a49deedaeb2bcd7"}, - {file = "librt-0.10.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:786ad5a15e99d0e0e74f3adbeecc198a5ac58f340be07e984723d1e0074838de"}, - {file = "librt-0.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:075582d877a97ee3d8e77bda3689dbe617b14f6469224a2d80b4b6c38e3951aa"}, - {file = "librt-0.10.0-cp311-cp311-win32.whl", hash = "sha256:75ecdc3f5a90065aa2af2e574706c5495adc392520762dcf10b1aa716f0b8090"}, - {file = "librt-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:b6f6084884131d8a52cb9d7095ff2aa52c1e786d9fdaefab1fb4515415e9e083"}, - {file = "librt-0.10.0-cp311-cp311-win_arm64.whl", hash = "sha256:0140bd62151160047e89b2730cb6f8506cdac5127baa1afb9231e4dd3fe7f681"}, - {file = "librt-0.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b4b58a44b407e91f633dafee008de9ddea6aa2a555ed94929c099260910bd0ba"}, - {file = "librt-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:950b79b11762531bdf45a9df909d2f9a2a8445c70c88665c01d14c8511a27dc5"}, - {file = "librt-0.10.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4538453f51be197633b425912c150e25b0667252d3741c53e8368176d98d9d37"}, - {file = "librt-0.10.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:70b955f091beac93e994a0b7ec616934f63b3ea5c3d6d7af847562f935aceca7"}, - {file = "librt-0.10.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:483e685e06b6163728ba6c85d74315176be7190f432ec2a41226e5e14355d5f0"}, - {file = "librt-0.10.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ac53d946a009d1a38c44a60812708c9458fb2a239a5f630d8e625571386650f"}, - {file = "librt-0.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc8771c9fcf0ea894ca41fdc2abd83572c2fbda221f232d86e718614e57ff513"}, - {file = "librt-0.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:70805dbc5257892ac572f86290a61e3c8d90224ecce1a8b2d1f7ed51965417f4"}, - {file = "librt-0.10.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d3b4f300f7bcba6e2ff73fb8bef1898479e9772bfa2682998c636391633ec826"}, - {file = "librt-0.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:943bc943f92f4fb3408fae62485c6a3ad68ce4f2ee205643a39641525c19a276"}, - {file = "librt-0.10.0-cp312-cp312-win32.whl", hash = "sha256:6065c1a758fba1010b41401013903d3d5d2750eab425ddedd584abac31d0630e"}, - {file = "librt-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:d788ecbe208ab352dab0e105cc06057bf9a2fc7e58cabb0d751ad9e30062b9e2"}, - {file = "librt-0.10.0-cp312-cp312-win_arm64.whl", hash = "sha256:6003d1f295bdba02656dc81308208fc060d0a51d8c0d0a6db70f7f3c57b9ba0a"}, - {file = "librt-0.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f0ede79d682e73f91c1b599a76d78b7464b9b5d213754cedb13372d9df36e596"}, - {file = "librt-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0ba0b131fdb336c8b9c948e397f4a7e649d0f783b529f07b647bf4961df392e"}, - {file = "librt-0.10.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2728117da2afb96fb957768725ee43dc9a2d73b031e02da424b818a3cdd3a275"}, - {file = "librt-0.10.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:723ba80594c49cdf0584196fc430752262605dc9449902fc9bd3d9b79976cb77"}, - {file = "librt-0.10.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7292edaaca294a61a978c53a3c7d6130d099b0dfbc8f0a65916cdc6b891b9852"}, - {file = "librt-0.10.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:89fe9d539f2c10a1666633eeeac507ce95dd06d9ecc58de3c6390dba156a3d3a"}, - {file = "librt-0.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4efa7b9587503fa5b67f40593302b9c8836d211d222ff9f7cafe67be5f8f0b10"}, - {file = "librt-0.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:22dc982ef59df0136df36092ccbdbb570ced8aafb33e49585739b2f1de1c13b6"}, - {file = "librt-0.10.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6f2e5f3606253a84cea719c94a3bb1c54487b5d617d0254d46e0920d8a06be3f"}, - {file = "librt-0.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:40884bfaa1e29f6b6a9be255007d8f359bfc9e61d68bdef8ed3158bfcbc95df9"}, - {file = "librt-0.10.0-cp313-cp313-win32.whl", hash = "sha256:3cd34cd8254eba756660bff6c2da91278248184301054fe3e4feb073bdd49b14"}, - {file = "librt-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:7baac5313e2d8dce1386f97777a8d03ab28f5fe1e780b3b9ac2ee7544551fedc"}, - {file = "librt-0.10.0-cp313-cp313-win_arm64.whl", hash = "sha256:afc5b4406c8e2515698d922a5c7823a009312835ea58196671fff40e35cb8166"}, - {file = "librt-0.10.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f09588a30e6a22ec624090d72a3ab1a6d4d5485c3ed739603e76aa3c16efa688"}, - {file = "librt-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:131ade118d12bd7a0adc4e655474a553f1b76cf78385868885944d21d51e45e0"}, - {file = "librt-0.10.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8b9ab28e40d011c373a189eae900c916e66d6fbecf7983e9e4883089ee085ef"}, - {file = "librt-0.10.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:67c39bb30da73bae1f293d1ed8bc2f8f6642649dd0928d3600aeff3041ac23d6"}, - {file = "librt-0.10.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8c3273c6b774614f093c8927c2bf1b077d0fefde988fe98f46a333734e5597ab"}, - {file = "librt-0.10.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9dd7c1b86a4baa583ab5db977484b93a2c474e69e96ef3e9538387ea54229cb9"}, - {file = "librt-0.10.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a77385c5a202e831149f7ad03be9e67cf80e957e52c614e83dcb822c95222eb8"}, - {file = "librt-0.10.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c6a5eafa74b5655bad59886138ed68426f098a6beb8cb95a71f2cc3cd8bb33fe"}, - {file = "librt-0.10.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:1fc93d0439204c50ab4d1512611ce2c206f1b369b419f69c7c27c761561e3291"}, - {file = "librt-0.10.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:79e713c178bc7a744adfbee6b4619a288eecc0c914da2a9313a20255abe2f0cf"}, - {file = "librt-0.10.0-cp314-cp314-win32.whl", hash = "sha256:2eba9d955a68c41d9f326be3da42f163ec3518b7ab20f1c826224e7bed71e0bf"}, - {file = "librt-0.10.0-cp314-cp314-win_amd64.whl", hash = "sha256:cbfaf7f5145e9917f5d18bffa298eff6a19d74e7b8b11dabdca95785befe8dbf"}, - {file = "librt-0.10.0-cp314-cp314-win_arm64.whl", hash = "sha256:8d6d385d1969849a6b1397114df22714b6ded917bada98668e3e974dc663477e"}, - {file = "librt-0.10.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:6c3a82d3bd32631ef5c79922dfc028520c9ad840255979ab4d908271818039ee"}, - {file = "librt-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d64cc66005dc324c9bb1fa3fc2841f529002f6eb15966d55e46d430f56955a6a"}, - {file = "librt-0.10.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bb562cd28c88cd2c6a9a6c78f99dc39348d6b16c94adc25de0e574acf1176e9"}, - {file = "librt-0.10.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:b809aa2854d019c28773b03605df22adc675ee4f3f4402d673581313e8906119"}, - {file = "librt-0.10.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc15acabdd519bd4176fdadc2119e5e3093485d86f89138daf47e5b4cedb983a"}, - {file = "librt-0.10.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b1b2d835307d08ddadd94568e2369648ec9173bd3eea6d7f52a1abe717c81f98"}, - {file = "librt-0.10.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d261c6a2f93335a5167887fb0223e8b98ffce20ee3fde242e8e58a37ece6d0e5"}, - {file = "librt-0.10.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e2ffd44963f8e7f68995504d90f9881d64e94dc1d8e310039b9526108fc0c0f7"}, - {file = "librt-0.10.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5f285f6455ed495791c4d8630e5af732960adea93cac4c893d15619f2eae53e8"}, - {file = "librt-0.10.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f6034ff52e663d34c7b82ef2aa2f94ad7c1d939e2368e63b06844bc4d127d2e1"}, - {file = "librt-0.10.0-cp314-cp314t-win32.whl", hash = "sha256:657860fd877fba6a241ea088ef99f63ca819945d3c715265da670bad56c37ebe"}, - {file = "librt-0.10.0-cp314-cp314t-win_amd64.whl", hash = "sha256:56ded2d66010203a0cb5af063b609e3f079531a0e5e576d618dece859fd2e1af"}, - {file = "librt-0.10.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1ee63f30abf18ed4830fdbaf87b2b6f4bba1e198d46085c314edde4045e56715"}, - {file = "librt-0.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:83628c28545a5f4d860b48fae7f62367c006ab7405898573f34af8b7dcb178a2"}, - {file = "librt-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bcf57b4de07e2d4bd093636ee59dc1b64298f304148dd9c4f001f7c7897650d"}, - {file = "librt-0.10.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2236c16bdb7c527eb671e4b599eec2c4229fddf80573de2bde529924f46db971"}, - {file = "librt-0.10.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:c1efa2f494811b245427095225a4d0251aee33ba4cf6ba2b7a6a9a619bc1a2ff"}, - {file = "librt-0.10.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d14626d350af79eed4b4f8886530052e3f78a62e9e53d2699f726f99c3d1d122"}, - {file = "librt-0.10.0-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b609f3461beae5608ca5219131ae5cdfea2e369818030abfc6ba7086830cde42"}, - {file = "librt-0.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0e2338b67c8e72755ccd1ab77b027e3701b375a1e12b4576fdefdf9c46448274"}, - {file = "librt-0.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:17cadff57139ff49beea0b17e50b28dfc3f9687126399696de4d2d8ae86ba7ff"}, - {file = "librt-0.10.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:5496102c8ed065c128d0f0fd10dcb3f9f3fd9b346954462d62af623f1b1ec7cd"}, - {file = "librt-0.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:537e1bfa459c1c92a263768a8a0c6fd0558049fa6c1b866d791eea711ae64114"}, - {file = "librt-0.10.0-cp39-cp39-win32.whl", hash = "sha256:85aca5a7ddc5f2d4cba24eba35667d83893ff2980dbd5884be16f538a24351e4"}, - {file = "librt-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:e45e46ff5fdfc690e77bb8557d5ba56974c4006b744ddbd70cce99fec6bfbeb8"}, - {file = "librt-0.10.0.tar.gz", hash = "sha256:1aba1e8aa4e3307a7be68a74149545fde7451964dc0235a8bec5704a17bdda42"}, -] - -[[package]] -name = "markdown" -version = "3.10.2" -description = "Python implementation of John Gruber's Markdown." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36"}, - {file = "markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950"}, -] - -[package.extras] -docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python] (>=0.28.3)"] -testing = ["coverage", "pyyaml"] - -[[package]] -name = "markupsafe" -version = "3.0.3" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, - {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, - {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, - {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, - {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, - {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, - {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, - {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, - {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, - {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, - {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, - {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, - {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, - {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, - {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, - {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, - {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, - {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, - {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, - {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, - {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, - {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, - {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, - {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, - {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, - {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, - {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, - {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, - {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, - {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, - {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, - {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, - {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, - {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, - {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, - {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, - {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, - {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, - {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, - {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, - {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, - {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, - {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, - {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, - {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, - {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, - {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, - {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, - {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, - {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, - {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, - {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, - {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, - {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, - {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, - {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, - {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, - {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, - {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, - {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, - {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, - {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, - {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, - {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, - {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, - {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, - {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, - {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, - {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, - {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, - {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, - {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, - {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, - {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, - {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, - {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, - {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, - {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, - {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, - {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, - {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, - {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, - {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, - {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, - {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, - {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, - {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, - {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, - {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, -] - -[[package]] -name = "mergedeep" -version = "1.3.4" -description = "A deep merge function for 🐍." -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, - {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, -] - -[[package]] -name = "mkdocs" -version = "1.6.1" -description = "Project documentation with Markdown." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, - {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, -] - -[package.dependencies] -click = ">=7.0" -colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} -ghp-import = ">=1.0" -jinja2 = ">=2.11.1" -markdown = ">=3.3.6" -markupsafe = ">=2.0.1" -mergedeep = ">=1.3.4" -mkdocs-get-deps = ">=0.2.0" -packaging = ">=20.5" -pathspec = ">=0.11.1" -pyyaml = ">=5.1" -pyyaml-env-tag = ">=0.1" -watchdog = ">=2.0" - -[package.extras] -i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] - -[[package]] -name = "mkdocs-get-deps" -version = "0.2.2" -description = "An extra command for MkDocs that infers required PyPI packages from `plugins` in mkdocs.yml" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650"}, - {file = "mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1"}, -] - -[package.dependencies] -mergedeep = ">=1.3.4" -platformdirs = ">=2.2.0" -pyyaml = ">=5.1" - -[[package]] -name = "mypy" -version = "1.20.2" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "mypy-1.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cf5a4db6dca263010e2c7bff081c89383c72d187ba2cf4c44759aac970e2f0c4"}, - {file = "mypy-1.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b0e817b518bff7facd7f85ea05b643ad8bdcce684cf29784987b0a7c8e1f997"}, - {file = "mypy-1.20.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97d7b9a485b40f8ca425460e89bf1da2814625b2da627c0dcc6aa46c92631d14"}, - {file = "mypy-1.20.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e1c12f6d2db3d78b909b5f77513c11eb7f2dd2782b96a3ab6dffc7d44575c99"}, - {file = "mypy-1.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:89dce27e142d25ffbc154c1819383b69f2e9234dc4ed4766f42e0e8cb264ab5c"}, - {file = "mypy-1.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:f376e37f9bf2a946872fc5fd1199c99310748e3c26c7a26683f13f8bdb756cbd"}, - {file = "mypy-1.20.2-cp310-cp310-win_arm64.whl", hash = "sha256:6e2b469efd811707bc530fd1effef0f5d6eebcb7fe376affae69025da4b979a2"}, - {file = "mypy-1.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4077797a273e56e8843d001e9dfe4ba10e33323d6ade647ff260e5cd97d9758c"}, - {file = "mypy-1.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cdecf62abcc4292500d7858aeae87a1f8f1150f4c4dd08fb0b336ee79b2a6df3"}, - {file = "mypy-1.20.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c566c3a88b6ece59b3d70f65bedef17304f48eb52ff040a6a18214e1917b3254"}, - {file = "mypy-1.20.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0deb80d062b2479f2c87ae568f89845afc71d11bc41b04179e58165fd9f31e98"}, - {file = "mypy-1.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bba9ad231e92a3e424b3e56b65aa17704993425bba97e302c832f9466bb85bac"}, - {file = "mypy-1.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:baf593f2765fa3a6b1ef95807dbaa3d25b594f6a52adcc506a6b9cb115e1be67"}, - {file = "mypy-1.20.2-cp311-cp311-win_arm64.whl", hash = "sha256:20175a1c0f49863946ec20b7f63255768058ac4f07d2b9ded6a6b46cfb5a9100"}, - {file = "mypy-1.20.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4dbfcf869f6b0517f70cf0030ba6ea1d6645e132337a7d5204a18d8d5636c02b"}, - {file = "mypy-1.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b6481b228d072315b053210b01ac320e1be243dc17f9e5887ef167f23f5fae4"}, - {file = "mypy-1.20.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34397cdced6b90b836e38182076049fdb41424322e0b0728c946b0939ebdf9f6"}, - {file = "mypy-1.20.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5da6976f20cae27059ea8d0c86e7cef3de720e04c4bb9ee18e3690fdb792066"}, - {file = "mypy-1.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:56908d7e08318d39f85b1f0c6cfd47b0cac1a130da677630dac0de3e0623e102"}, - {file = "mypy-1.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:d52ad8d78522da1d308789df651ee5379088e77c76cb1994858d40a426b343b9"}, - {file = "mypy-1.20.2-cp312-cp312-win_arm64.whl", hash = "sha256:785b08db19c9f214dc37d65f7c165d19a30fcecb48abfa30f31b01b5acaabb58"}, - {file = "mypy-1.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:edfbfca868cdd6bd8d974a60f8a3682f5565d3f5c99b327640cedd24c4264026"}, - {file = "mypy-1.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e2877a02380adfcdbc69071a0f74d6e9dbbf593c0dc9d174e1f223ffd5281943"}, - {file = "mypy-1.20.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7488448de6007cd5177c6cea0517ac33b4c0f5ee9b5e9f2be51ce75511a85517"}, - {file = "mypy-1.20.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb9c2fa06887e21d6a3a868762acb82aec34e2c6fd0174064f27c93ede68ad15"}, - {file = "mypy-1.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d56a78b646f2e3daa865bc70cd5ec5a46c50045801ca8ff17a0c43abc97e3ee"}, - {file = "mypy-1.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:2a4102b03bb7481d9a91a6da8d174740c9c8c4401024684b9ca3b7cc5e49852f"}, - {file = "mypy-1.20.2-cp313-cp313-win_arm64.whl", hash = "sha256:a95a9248b0c6fd933a442c03c3b113c3b61320086b88e2c444676d3fd1ca3330"}, - {file = "mypy-1.20.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:419413398fe250aae057fd2fe50166b61077083c9b82754c341cf4fd73038f30"}, - {file = "mypy-1.20.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e73c07f23009962885c197ccb9b41356a30cc0e5a1d0c2ea8fd8fb1362d7f924"}, - {file = "mypy-1.20.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c64e5973df366b747646fc98da921f9d6eba9716d57d1db94a83c026a08e0fb"}, - {file = "mypy-1.20.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a65aa591af023864fd08a97da9974e919452cfe19cb146c8a5dc692626445dc"}, - {file = "mypy-1.20.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fef51b01e638974a6e69885687e9bd40c8d1e09a6cd291cca0619625cf1f558"}, - {file = "mypy-1.20.2-cp314-cp314-win_amd64.whl", hash = "sha256:913485a03f1bcf5d279409a9d2b9ed565c151f61c09f29991e5faa14033da4c8"}, - {file = "mypy-1.20.2-cp314-cp314-win_arm64.whl", hash = "sha256:c3bae4f855d965b5453784300c12ffc63a548304ac7f99e55d4dc7c898673aa3"}, - {file = "mypy-1.20.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2de3dcea53babc1c3237a19002bc3d228ce1833278f093b8d619e06e7cc79609"}, - {file = "mypy-1.20.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:52b176444e2e5054dfcbcb8c75b0b719865c96247b37407184bbfca5c353f2c2"}, - {file = "mypy-1.20.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:688c3312e5dadb573a2c69c82af3a298d43ecf9e6d264e0f95df960b5f6ac19c"}, - {file = "mypy-1.20.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29752dbbf8cc53f89f6ac096d363314333045c257c9c75cbd189ca2de0455744"}, - {file = "mypy-1.20.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:803203d2b6ea644982c644895c2f78b28d0e208bba7b27d9b921e0ec5eb207c6"}, - {file = "mypy-1.20.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9bcb8aa397ff0093c824182fd76a935a9ba7ad097fcbef80ae89bf6c1731d8ec"}, - {file = "mypy-1.20.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e061b58443f1736f8a37c48978d7ab581636d6ab03e3d4f99e3fa90463bb9382"}, - {file = "mypy-1.20.2-py3-none-any.whl", hash = "sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563"}, - {file = "mypy-1.20.2.tar.gz", hash = "sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665"}, -] - -[package.dependencies] -librt = {version = ">=0.8.0", markers = "platform_python_implementation != \"PyPy\""} -mypy_extensions = ">=1.0.0" -pathspec = ">=1.0.0" -typing_extensions = [ - {version = ">=4.6.0", markers = "python_version < \"3.15\""}, - {version = ">=4.14.0", markers = "python_version >= \"3.15\""}, -] - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -native-parser = ["ast-serialize (>=0.1.1,<1.0.0)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, - {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, -] - -[[package]] -name = "nodeenv" -version = "1.10.0" -description = "Node.js virtual environment builder" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -files = [ - {file = "nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827"}, - {file = "nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb"}, -] - -[[package]] -name = "packaging" -version = "26.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e"}, - {file = "packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661"}, -] - -[[package]] -name = "pastel" -version = "0.2.1" -description = "Bring colors to your terminal." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -groups = ["dev"] -files = [ - {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, - {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, -] - -[[package]] -name = "pathspec" -version = "1.1.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189"}, - {file = "pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a"}, -] - -[package.extras] -hyperscan = ["hyperscan (>=0.7)"] -optional = ["typing-extensions (>=4)"] -re2 = ["google-re2 (>=1.1)"] - -[[package]] -name = "platformdirs" -version = "4.9.6" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917"}, - {file = "platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a"}, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, - {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["coverage", "pytest", "pytest-benchmark"] - -[[package]] -name = "poethepoet" -version = "0.45.0" -description = "A task runner that works well with poetry and uv." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "poethepoet-0.45.0-py3-none-any.whl", hash = "sha256:8e25f6e834ecf25fe2ddca676a4e0207eeb2e19def0a8709fc5c7f18e86cd68c"}, - {file = "poethepoet-0.45.0.tar.gz", hash = "sha256:cf2c2df4c185d33b619c2771de2b28c2b81c733f072226030c7fa4c273c040f8"}, -] - -[package.dependencies] -pastel = ">=0.2.1,<0.3.0" -pyyaml = ">=6.0.3,<7.0" - -[package.extras] -poetry-plugin = ["poetry (>=1.2.0,<3.0.0) ; python_version < \"4.0\""] - -[[package]] -name = "pre-commit" -version = "4.6.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b"}, - {file = "pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - -[[package]] -name = "pygments" -version = "2.20.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, - {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pytest" -version = "9.0.3" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"}, - {file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"}, -] - -[package.dependencies] -colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -iniconfig = ">=1.0.1" -packaging = ">=22" -pluggy = ">=1.5,<2" -pygments = ">=2.7.2" - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-cov" -version = "7.1.0" -description = "Pytest plugin for measuring coverage." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678"}, - {file = "pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2"}, -] - -[package.dependencies] -coverage = {version = ">=7.10.6", extras = ["toml"]} -pluggy = ">=1.2" -pytest = ">=7" - -[package.extras] -testing = ["process-tests", "pytest-xdist", "virtualenv"] - -[[package]] -name = "pytest-xdist" -version = "3.8.0" -description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, - {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, -] - -[package.dependencies] -execnet = ">=2.1" -pytest = ">=7.0.0" - -[package.extras] -psutil = ["psutil (>=3.0)"] -setproctitle = ["setproctitle"] -testing = ["filelock"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["dev"] -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-discovery" -version = "1.3.0" -description = "Python interpreter discovery" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "python_discovery-1.3.0-py3-none-any.whl", hash = "sha256:441d9ced3dfce36e113beb35ca302c71c7ef06f3c0f9c227a0b9bb3bd49b9e9f"}, - {file = "python_discovery-1.3.0.tar.gz", hash = "sha256:d098f1e86be5d45fe4d14bf1029294aabbd332f4321179dec85e76cddce834b0"}, -] - -[package.dependencies] -filelock = ">=3.15.4" -platformdirs = ">=4.3.6,<5" - -[package.extras] -docs = ["furo (>=2025.12.19)", "sphinx (>=9.1)", "sphinx-autodoc-typehints (>=3.6.3)", "sphinxcontrib-mermaid (>=2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.5.4)", "pytest (>=8.3.5)", "pytest-mock (>=3.14)", "setuptools (>=75.1)"] - -[[package]] -name = "pyyaml" -version = "6.0.3" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, - {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, - {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"}, - {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"}, - {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"}, - {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"}, - {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"}, - {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, - {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, - {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, - {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, - {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, - {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, - {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, - {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, - {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, - {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, - {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, - {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, - {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, - {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, - {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, - {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, - {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, - {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, - {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, - {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, - {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, - {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, - {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, - {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, - {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, - {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, - {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, - {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, - {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, - {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, - {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, - {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, - {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, - {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, - {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, - {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, - {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, - {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, - {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, - {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, - {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, - {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, - {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, - {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, - {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, - {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, - {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, - {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, - {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, - {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, - {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, - {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, - {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, - {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, - {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, - {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, - {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, - {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, - {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, - {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, - {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, - {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, - {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, - {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, - {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, - {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, -] - -[[package]] -name = "pyyaml-env-tag" -version = "1.1" -description = "A custom YAML tag for referencing environment variables in YAML files." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"}, - {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"}, -] - -[package.dependencies] -pyyaml = "*" - -[[package]] -name = "ruff" -version = "0.15.12" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c"}, - {file = "ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c"}, - {file = "ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5"}, - {file = "ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002"}, - {file = "ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5"}, - {file = "ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6"}, - {file = "ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33"}, - {file = "ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847"}, - {file = "ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0"}, - {file = "ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339"}, - {file = "ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5"}, - {file = "ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd"}, - {file = "ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b"}, - {file = "ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e"}, - {file = "ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20"}, - {file = "ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d"}, - {file = "ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f"}, - {file = "ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6"}, -] - -[[package]] -name = "setuptools" -version = "78.1.1" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"}, - {file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] -core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] - -[[package]] -name = "six" -version = "1.17.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["dev"] -files = [ - {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, - {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -description = "Backported and Experimental Type Hints for Python 3.9+" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, -] - -[[package]] -name = "virtualenv" -version = "21.3.1" -description = "Virtual Python Environment builder" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "virtualenv-21.3.1-py3-none-any.whl", hash = "sha256:d1a71cf58f2f9228fff23a1f6ec15d39785c6b32e03658d104974247145edd35"}, - {file = "virtualenv-21.3.1.tar.gz", hash = "sha256:c2305bc1fddeec40699b8370d13f8d431b0701f00ce895061ce493aeded4426b"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = {version = ">=3.24.2,<4", markers = "python_version >= \"3.10\""} -platformdirs = ">=3.9.1,<5" -python-discovery = ">=1.2.2" - -[[package]] -name = "watchdog" -version = "6.0.0" -description = "Filesystem events monitoring" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, - {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, - {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, - {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, - {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, - {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, - {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, - {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, - {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, - {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, - {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, -] - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[metadata] -lock-version = "2.1" -python-versions = ">=3.12" -content-hash = "aa734f52ed314d0af56c786d5eae67b0964d6d199359f642a10418b5cafd1866" +# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. + +[[package]] +name = "bump2version" +version = "1.0.1" +description = "Version-bump your software with a single command!" +optional = false +python-versions = ">=3.5" +groups = ["dev"] +files = [ + {file = "bump2version-1.0.1-py2.py3-none-any.whl", hash = "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410"}, + {file = "bump2version-1.0.1.tar.gz", hash = "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6"}, +] + +[[package]] +name = "cfgv" +version = "3.5.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0"}, + {file = "cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132"}, +] + +[[package]] +name = "click" +version = "8.3.3" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613"}, + {file = "click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\" or platform_system == \"Windows\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.13.5" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "coverage-7.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0723d2c96324561b9aa76fb982406e11d93cdb388a7a7da2b16e04719cf7ca5"}, + {file = "coverage-7.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52f444e86475992506b32d4e5ca55c24fc88d73bcbda0e9745095b28ef4dc0cf"}, + {file = "coverage-7.13.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:704de6328e3d612a8f6c07000a878ff38181ec3263d5a11da1db294fa6a9bdf8"}, + {file = "coverage-7.13.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1a6d79a14e1ec1832cabc833898636ad5f3754a678ef8bb4908515208bf84f4"}, + {file = "coverage-7.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79060214983769c7ba3f0cee10b54c97609dca4d478fa1aa32b914480fd5738d"}, + {file = "coverage-7.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:356e76b46783a98c2a2fe81ec79df4883a1e62895ea952968fb253c114e7f930"}, + {file = "coverage-7.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cef0cdec915d11254a7f549c1170afecce708d30610c6abdded1f74e581666d"}, + {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc022073d063b25a402454e5712ef9e007113e3a676b96c5f29b2bda29352f40"}, + {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b74db26dfea4f4e50d48a4602207cd1e78be33182bc9cbf22da94f332f99878"}, + {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad146744ca4fd09b50c482650e3c1b1f4dfa1d4792e0a04a369c7f23336f0400"}, + {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c555b48be1853fe3997c11c4bd521cdd9a9612352de01fa4508f16ec341e6fe0"}, + {file = "coverage-7.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7034b5c56a58ae5e85f23949d52c14aca2cfc6848a31764995b7de88f13a1ea0"}, + {file = "coverage-7.13.5-cp310-cp310-win32.whl", hash = "sha256:eb7fdf1ef130660e7415e0253a01a7d5a88c9c4d158bcf75cbbd922fd65a5b58"}, + {file = "coverage-7.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:3e1bb5f6c78feeb1be3475789b14a0f0a5b47d505bfc7267126ccbd50289999e"}, + {file = "coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d"}, + {file = "coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587"}, + {file = "coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642"}, + {file = "coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b"}, + {file = "coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686"}, + {file = "coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743"}, + {file = "coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75"}, + {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209"}, + {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a"}, + {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e"}, + {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd"}, + {file = "coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8"}, + {file = "coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf"}, + {file = "coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9"}, + {file = "coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028"}, + {file = "coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01"}, + {file = "coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422"}, + {file = "coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f"}, + {file = "coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5"}, + {file = "coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376"}, + {file = "coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256"}, + {file = "coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c"}, + {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5"}, + {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09"}, + {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9"}, + {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf"}, + {file = "coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c"}, + {file = "coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf"}, + {file = "coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810"}, + {file = "coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de"}, + {file = "coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1"}, + {file = "coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3"}, + {file = "coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26"}, + {file = "coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3"}, + {file = "coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b"}, + {file = "coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a"}, + {file = "coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969"}, + {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161"}, + {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15"}, + {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1"}, + {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6"}, + {file = "coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17"}, + {file = "coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85"}, + {file = "coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b"}, + {file = "coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664"}, + {file = "coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d"}, + {file = "coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0"}, + {file = "coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806"}, + {file = "coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3"}, + {file = "coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9"}, + {file = "coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd"}, + {file = "coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606"}, + {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e"}, + {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0"}, + {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87"}, + {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479"}, + {file = "coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2"}, + {file = "coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a"}, + {file = "coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819"}, + {file = "coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911"}, + {file = "coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f"}, + {file = "coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e"}, + {file = "coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a"}, + {file = "coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510"}, + {file = "coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247"}, + {file = "coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6"}, + {file = "coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0"}, + {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882"}, + {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740"}, + {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16"}, + {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0"}, + {file = "coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0"}, + {file = "coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc"}, + {file = "coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633"}, + {file = "coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8"}, + {file = "coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b"}, + {file = "coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c"}, + {file = "coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9"}, + {file = "coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29"}, + {file = "coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607"}, + {file = "coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90"}, + {file = "coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3"}, + {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab"}, + {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562"}, + {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2"}, + {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea"}, + {file = "coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a"}, + {file = "coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215"}, + {file = "coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43"}, + {file = "coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45"}, + {file = "coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61"}, + {file = "coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179"}, +] + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + +[[package]] +name = "distlib" +version = "0.4.0" +description = "Distribution utilities" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, + {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, +] + +[[package]] +name = "execnet" +version = "2.1.2" +description = "execnet: rapid multi-Python deployment" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec"}, + {file = "execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd"}, +] + +[package.extras] +testing = ["hatch", "pre-commit", "pytest", "tox"] + +[[package]] +name = "filelock" +version = "3.29.0" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258"}, + {file = "filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90"}, +] + +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + +[[package]] +name = "identify" +version = "2.6.19" +description = "File identification library for Python" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a"}, + {file = "identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "iniconfig" +version = "2.3.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, + {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "librt" +version = "0.10.0" +description = "Mypyc runtime library" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +markers = "platform_python_implementation != \"PyPy\"" +files = [ + {file = "librt-0.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dc99f9642100b86e5f6bb14cdc9970009e31a9ef7d64df6704b7018451524a3"}, + {file = "librt-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8298cedfcfaff3790000bd057aaaa3df1b0ab54cf7b48eeab16184cbb1bc66b9"}, + {file = "librt-0.10.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7dbe312dbf76468255b79a7ba311236fde620f2f7055fc09d421e31340314e"}, + {file = "librt-0.10.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:56ed90c48c19249012dadfd79a1bc13bd5168ea60a70722d330a3a600c0b1852"}, + {file = "librt-0.10.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d74ca0f4b2b09c117f913d4df01f6b934dff8a271096b35167d5264a31649f0"}, + {file = "librt-0.10.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8eb2daa9375f93c0e55ff5e44a4bbe98f39e5fe52e1abf9c97acb67743b61bf8"}, + {file = "librt-0.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7b09b90e634e6dff57978cd358070046071e2b120501f10787aeb35425f504f6"}, + {file = "librt-0.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:2cf22fd379d60c739b800d4295ed34045f8b04aa8df9c12bd2f8f43f7fe672b7"}, + {file = "librt-0.10.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:74c798793fcf29a84d442278ebe0bb1fff79fe58ac4106eeff7019cbba861423"}, + {file = "librt-0.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dc4f1573401e8dbe6c26511fe027620b0fb30ae9a7ab814e02e510626b8b5f9c"}, + {file = "librt-0.10.0-cp310-cp310-win32.whl", hash = "sha256:e1428275f5fe3d4db6822e58d8b005a5b28ffca55e8433ebc051247fbe46429f"}, + {file = "librt-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:0708e9408f585b0f065081680583a577652099680ccf820c7538904322b679c3"}, + {file = "librt-0.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:01b4500ca3a625450c032a9142a8e843923ce263fa8a92ad1b38927cabe2fe72"}, + {file = "librt-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b7e42d1b3e300d20bfc87e72ffd62f0a92a2cb3c35f7bf90df90c9d2a49f74c"}, + {file = "librt-0.10.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8ef7b8c61ce3a1b597cd3e15348ff1574325165c2e7ce09a718154cde2a7950"}, + {file = "librt-0.10.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:e73c84f72d1fa0d6eaa7a1930b436ba8d2c90c58d77bfabb09995a69ad35f6c0"}, + {file = "librt-0.10.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9728cb98713bd862fb8f4fd6a642d1896c86058a41d77c70f3d5cee75e725275"}, + {file = "librt-0.10.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:648b7e941d20acd72f9652115e0e53facd98156d61f9ebf7a812bdef8bdccea9"}, + {file = "librt-0.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c3e33747c068e86a9007c20fdb777eb5ba8d3d19136d7812f88e69a713041b6f"}, + {file = "librt-0.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d509c745bf7e77d1107cf05e6abb249dc03fad13eb39f2286a49deedaeb2bcd7"}, + {file = "librt-0.10.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:786ad5a15e99d0e0e74f3adbeecc198a5ac58f340be07e984723d1e0074838de"}, + {file = "librt-0.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:075582d877a97ee3d8e77bda3689dbe617b14f6469224a2d80b4b6c38e3951aa"}, + {file = "librt-0.10.0-cp311-cp311-win32.whl", hash = "sha256:75ecdc3f5a90065aa2af2e574706c5495adc392520762dcf10b1aa716f0b8090"}, + {file = "librt-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:b6f6084884131d8a52cb9d7095ff2aa52c1e786d9fdaefab1fb4515415e9e083"}, + {file = "librt-0.10.0-cp311-cp311-win_arm64.whl", hash = "sha256:0140bd62151160047e89b2730cb6f8506cdac5127baa1afb9231e4dd3fe7f681"}, + {file = "librt-0.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b4b58a44b407e91f633dafee008de9ddea6aa2a555ed94929c099260910bd0ba"}, + {file = "librt-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:950b79b11762531bdf45a9df909d2f9a2a8445c70c88665c01d14c8511a27dc5"}, + {file = "librt-0.10.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4538453f51be197633b425912c150e25b0667252d3741c53e8368176d98d9d37"}, + {file = "librt-0.10.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:70b955f091beac93e994a0b7ec616934f63b3ea5c3d6d7af847562f935aceca7"}, + {file = "librt-0.10.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:483e685e06b6163728ba6c85d74315176be7190f432ec2a41226e5e14355d5f0"}, + {file = "librt-0.10.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ac53d946a009d1a38c44a60812708c9458fb2a239a5f630d8e625571386650f"}, + {file = "librt-0.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc8771c9fcf0ea894ca41fdc2abd83572c2fbda221f232d86e718614e57ff513"}, + {file = "librt-0.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:70805dbc5257892ac572f86290a61e3c8d90224ecce1a8b2d1f7ed51965417f4"}, + {file = "librt-0.10.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d3b4f300f7bcba6e2ff73fb8bef1898479e9772bfa2682998c636391633ec826"}, + {file = "librt-0.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:943bc943f92f4fb3408fae62485c6a3ad68ce4f2ee205643a39641525c19a276"}, + {file = "librt-0.10.0-cp312-cp312-win32.whl", hash = "sha256:6065c1a758fba1010b41401013903d3d5d2750eab425ddedd584abac31d0630e"}, + {file = "librt-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:d788ecbe208ab352dab0e105cc06057bf9a2fc7e58cabb0d751ad9e30062b9e2"}, + {file = "librt-0.10.0-cp312-cp312-win_arm64.whl", hash = "sha256:6003d1f295bdba02656dc81308208fc060d0a51d8c0d0a6db70f7f3c57b9ba0a"}, + {file = "librt-0.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f0ede79d682e73f91c1b599a76d78b7464b9b5d213754cedb13372d9df36e596"}, + {file = "librt-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0ba0b131fdb336c8b9c948e397f4a7e649d0f783b529f07b647bf4961df392e"}, + {file = "librt-0.10.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2728117da2afb96fb957768725ee43dc9a2d73b031e02da424b818a3cdd3a275"}, + {file = "librt-0.10.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:723ba80594c49cdf0584196fc430752262605dc9449902fc9bd3d9b79976cb77"}, + {file = "librt-0.10.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7292edaaca294a61a978c53a3c7d6130d099b0dfbc8f0a65916cdc6b891b9852"}, + {file = "librt-0.10.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:89fe9d539f2c10a1666633eeeac507ce95dd06d9ecc58de3c6390dba156a3d3a"}, + {file = "librt-0.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4efa7b9587503fa5b67f40593302b9c8836d211d222ff9f7cafe67be5f8f0b10"}, + {file = "librt-0.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:22dc982ef59df0136df36092ccbdbb570ced8aafb33e49585739b2f1de1c13b6"}, + {file = "librt-0.10.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6f2e5f3606253a84cea719c94a3bb1c54487b5d617d0254d46e0920d8a06be3f"}, + {file = "librt-0.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:40884bfaa1e29f6b6a9be255007d8f359bfc9e61d68bdef8ed3158bfcbc95df9"}, + {file = "librt-0.10.0-cp313-cp313-win32.whl", hash = "sha256:3cd34cd8254eba756660bff6c2da91278248184301054fe3e4feb073bdd49b14"}, + {file = "librt-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:7baac5313e2d8dce1386f97777a8d03ab28f5fe1e780b3b9ac2ee7544551fedc"}, + {file = "librt-0.10.0-cp313-cp313-win_arm64.whl", hash = "sha256:afc5b4406c8e2515698d922a5c7823a009312835ea58196671fff40e35cb8166"}, + {file = "librt-0.10.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f09588a30e6a22ec624090d72a3ab1a6d4d5485c3ed739603e76aa3c16efa688"}, + {file = "librt-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:131ade118d12bd7a0adc4e655474a553f1b76cf78385868885944d21d51e45e0"}, + {file = "librt-0.10.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8b9ab28e40d011c373a189eae900c916e66d6fbecf7983e9e4883089ee085ef"}, + {file = "librt-0.10.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:67c39bb30da73bae1f293d1ed8bc2f8f6642649dd0928d3600aeff3041ac23d6"}, + {file = "librt-0.10.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8c3273c6b774614f093c8927c2bf1b077d0fefde988fe98f46a333734e5597ab"}, + {file = "librt-0.10.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9dd7c1b86a4baa583ab5db977484b93a2c474e69e96ef3e9538387ea54229cb9"}, + {file = "librt-0.10.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a77385c5a202e831149f7ad03be9e67cf80e957e52c614e83dcb822c95222eb8"}, + {file = "librt-0.10.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c6a5eafa74b5655bad59886138ed68426f098a6beb8cb95a71f2cc3cd8bb33fe"}, + {file = "librt-0.10.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:1fc93d0439204c50ab4d1512611ce2c206f1b369b419f69c7c27c761561e3291"}, + {file = "librt-0.10.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:79e713c178bc7a744adfbee6b4619a288eecc0c914da2a9313a20255abe2f0cf"}, + {file = "librt-0.10.0-cp314-cp314-win32.whl", hash = "sha256:2eba9d955a68c41d9f326be3da42f163ec3518b7ab20f1c826224e7bed71e0bf"}, + {file = "librt-0.10.0-cp314-cp314-win_amd64.whl", hash = "sha256:cbfaf7f5145e9917f5d18bffa298eff6a19d74e7b8b11dabdca95785befe8dbf"}, + {file = "librt-0.10.0-cp314-cp314-win_arm64.whl", hash = "sha256:8d6d385d1969849a6b1397114df22714b6ded917bada98668e3e974dc663477e"}, + {file = "librt-0.10.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:6c3a82d3bd32631ef5c79922dfc028520c9ad840255979ab4d908271818039ee"}, + {file = "librt-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d64cc66005dc324c9bb1fa3fc2841f529002f6eb15966d55e46d430f56955a6a"}, + {file = "librt-0.10.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bb562cd28c88cd2c6a9a6c78f99dc39348d6b16c94adc25de0e574acf1176e9"}, + {file = "librt-0.10.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:b809aa2854d019c28773b03605df22adc675ee4f3f4402d673581313e8906119"}, + {file = "librt-0.10.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc15acabdd519bd4176fdadc2119e5e3093485d86f89138daf47e5b4cedb983a"}, + {file = "librt-0.10.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b1b2d835307d08ddadd94568e2369648ec9173bd3eea6d7f52a1abe717c81f98"}, + {file = "librt-0.10.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d261c6a2f93335a5167887fb0223e8b98ffce20ee3fde242e8e58a37ece6d0e5"}, + {file = "librt-0.10.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e2ffd44963f8e7f68995504d90f9881d64e94dc1d8e310039b9526108fc0c0f7"}, + {file = "librt-0.10.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5f285f6455ed495791c4d8630e5af732960adea93cac4c893d15619f2eae53e8"}, + {file = "librt-0.10.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f6034ff52e663d34c7b82ef2aa2f94ad7c1d939e2368e63b06844bc4d127d2e1"}, + {file = "librt-0.10.0-cp314-cp314t-win32.whl", hash = "sha256:657860fd877fba6a241ea088ef99f63ca819945d3c715265da670bad56c37ebe"}, + {file = "librt-0.10.0-cp314-cp314t-win_amd64.whl", hash = "sha256:56ded2d66010203a0cb5af063b609e3f079531a0e5e576d618dece859fd2e1af"}, + {file = "librt-0.10.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1ee63f30abf18ed4830fdbaf87b2b6f4bba1e198d46085c314edde4045e56715"}, + {file = "librt-0.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:83628c28545a5f4d860b48fae7f62367c006ab7405898573f34af8b7dcb178a2"}, + {file = "librt-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bcf57b4de07e2d4bd093636ee59dc1b64298f304148dd9c4f001f7c7897650d"}, + {file = "librt-0.10.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2236c16bdb7c527eb671e4b599eec2c4229fddf80573de2bde529924f46db971"}, + {file = "librt-0.10.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:c1efa2f494811b245427095225a4d0251aee33ba4cf6ba2b7a6a9a619bc1a2ff"}, + {file = "librt-0.10.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d14626d350af79eed4b4f8886530052e3f78a62e9e53d2699f726f99c3d1d122"}, + {file = "librt-0.10.0-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b609f3461beae5608ca5219131ae5cdfea2e369818030abfc6ba7086830cde42"}, + {file = "librt-0.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0e2338b67c8e72755ccd1ab77b027e3701b375a1e12b4576fdefdf9c46448274"}, + {file = "librt-0.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:17cadff57139ff49beea0b17e50b28dfc3f9687126399696de4d2d8ae86ba7ff"}, + {file = "librt-0.10.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:5496102c8ed065c128d0f0fd10dcb3f9f3fd9b346954462d62af623f1b1ec7cd"}, + {file = "librt-0.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:537e1bfa459c1c92a263768a8a0c6fd0558049fa6c1b866d791eea711ae64114"}, + {file = "librt-0.10.0-cp39-cp39-win32.whl", hash = "sha256:85aca5a7ddc5f2d4cba24eba35667d83893ff2980dbd5884be16f538a24351e4"}, + {file = "librt-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:e45e46ff5fdfc690e77bb8557d5ba56974c4006b744ddbd70cce99fec6bfbeb8"}, + {file = "librt-0.10.0.tar.gz", hash = "sha256:1aba1e8aa4e3307a7be68a74149545fde7451964dc0235a8bec5704a17bdda42"}, +] + +[[package]] +name = "markdown" +version = "3.10.2" +description = "Python implementation of John Gruber's Markdown." +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36"}, + {file = "markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950"}, +] + +[package.extras] +docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python] (>=0.28.3)"] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markupsafe" +version = "3.0.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, + {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, + {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, + {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, + {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, + {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, + {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, + {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +description = "Project documentation with Markdown." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, + {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} +ghp-import = ">=1.0" +jinja2 = ">=2.11.1" +markdown = ">=3.3.6" +markupsafe = ">=2.0.1" +mergedeep = ">=1.3.4" +mkdocs-get-deps = ">=0.2.0" +packaging = ">=20.5" +pathspec = ">=0.11.1" +pyyaml = ">=5.1" +pyyaml-env-tag = ">=0.1" +watchdog = ">=2.0" + +[package.extras] +i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.2" +description = "An extra command for MkDocs that infers required PyPI packages from `plugins` in mkdocs.yml" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650"}, + {file = "mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1"}, +] + +[package.dependencies] +mergedeep = ">=1.3.4" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" + +[[package]] +name = "mypy" +version = "1.20.2" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "mypy-1.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cf5a4db6dca263010e2c7bff081c89383c72d187ba2cf4c44759aac970e2f0c4"}, + {file = "mypy-1.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b0e817b518bff7facd7f85ea05b643ad8bdcce684cf29784987b0a7c8e1f997"}, + {file = "mypy-1.20.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97d7b9a485b40f8ca425460e89bf1da2814625b2da627c0dcc6aa46c92631d14"}, + {file = "mypy-1.20.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e1c12f6d2db3d78b909b5f77513c11eb7f2dd2782b96a3ab6dffc7d44575c99"}, + {file = "mypy-1.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:89dce27e142d25ffbc154c1819383b69f2e9234dc4ed4766f42e0e8cb264ab5c"}, + {file = "mypy-1.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:f376e37f9bf2a946872fc5fd1199c99310748e3c26c7a26683f13f8bdb756cbd"}, + {file = "mypy-1.20.2-cp310-cp310-win_arm64.whl", hash = "sha256:6e2b469efd811707bc530fd1effef0f5d6eebcb7fe376affae69025da4b979a2"}, + {file = "mypy-1.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4077797a273e56e8843d001e9dfe4ba10e33323d6ade647ff260e5cd97d9758c"}, + {file = "mypy-1.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cdecf62abcc4292500d7858aeae87a1f8f1150f4c4dd08fb0b336ee79b2a6df3"}, + {file = "mypy-1.20.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c566c3a88b6ece59b3d70f65bedef17304f48eb52ff040a6a18214e1917b3254"}, + {file = "mypy-1.20.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0deb80d062b2479f2c87ae568f89845afc71d11bc41b04179e58165fd9f31e98"}, + {file = "mypy-1.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bba9ad231e92a3e424b3e56b65aa17704993425bba97e302c832f9466bb85bac"}, + {file = "mypy-1.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:baf593f2765fa3a6b1ef95807dbaa3d25b594f6a52adcc506a6b9cb115e1be67"}, + {file = "mypy-1.20.2-cp311-cp311-win_arm64.whl", hash = "sha256:20175a1c0f49863946ec20b7f63255768058ac4f07d2b9ded6a6b46cfb5a9100"}, + {file = "mypy-1.20.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4dbfcf869f6b0517f70cf0030ba6ea1d6645e132337a7d5204a18d8d5636c02b"}, + {file = "mypy-1.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b6481b228d072315b053210b01ac320e1be243dc17f9e5887ef167f23f5fae4"}, + {file = "mypy-1.20.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34397cdced6b90b836e38182076049fdb41424322e0b0728c946b0939ebdf9f6"}, + {file = "mypy-1.20.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5da6976f20cae27059ea8d0c86e7cef3de720e04c4bb9ee18e3690fdb792066"}, + {file = "mypy-1.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:56908d7e08318d39f85b1f0c6cfd47b0cac1a130da677630dac0de3e0623e102"}, + {file = "mypy-1.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:d52ad8d78522da1d308789df651ee5379088e77c76cb1994858d40a426b343b9"}, + {file = "mypy-1.20.2-cp312-cp312-win_arm64.whl", hash = "sha256:785b08db19c9f214dc37d65f7c165d19a30fcecb48abfa30f31b01b5acaabb58"}, + {file = "mypy-1.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:edfbfca868cdd6bd8d974a60f8a3682f5565d3f5c99b327640cedd24c4264026"}, + {file = "mypy-1.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e2877a02380adfcdbc69071a0f74d6e9dbbf593c0dc9d174e1f223ffd5281943"}, + {file = "mypy-1.20.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7488448de6007cd5177c6cea0517ac33b4c0f5ee9b5e9f2be51ce75511a85517"}, + {file = "mypy-1.20.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb9c2fa06887e21d6a3a868762acb82aec34e2c6fd0174064f27c93ede68ad15"}, + {file = "mypy-1.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d56a78b646f2e3daa865bc70cd5ec5a46c50045801ca8ff17a0c43abc97e3ee"}, + {file = "mypy-1.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:2a4102b03bb7481d9a91a6da8d174740c9c8c4401024684b9ca3b7cc5e49852f"}, + {file = "mypy-1.20.2-cp313-cp313-win_arm64.whl", hash = "sha256:a95a9248b0c6fd933a442c03c3b113c3b61320086b88e2c444676d3fd1ca3330"}, + {file = "mypy-1.20.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:419413398fe250aae057fd2fe50166b61077083c9b82754c341cf4fd73038f30"}, + {file = "mypy-1.20.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e73c07f23009962885c197ccb9b41356a30cc0e5a1d0c2ea8fd8fb1362d7f924"}, + {file = "mypy-1.20.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c64e5973df366b747646fc98da921f9d6eba9716d57d1db94a83c026a08e0fb"}, + {file = "mypy-1.20.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a65aa591af023864fd08a97da9974e919452cfe19cb146c8a5dc692626445dc"}, + {file = "mypy-1.20.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fef51b01e638974a6e69885687e9bd40c8d1e09a6cd291cca0619625cf1f558"}, + {file = "mypy-1.20.2-cp314-cp314-win_amd64.whl", hash = "sha256:913485a03f1bcf5d279409a9d2b9ed565c151f61c09f29991e5faa14033da4c8"}, + {file = "mypy-1.20.2-cp314-cp314-win_arm64.whl", hash = "sha256:c3bae4f855d965b5453784300c12ffc63a548304ac7f99e55d4dc7c898673aa3"}, + {file = "mypy-1.20.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2de3dcea53babc1c3237a19002bc3d228ce1833278f093b8d619e06e7cc79609"}, + {file = "mypy-1.20.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:52b176444e2e5054dfcbcb8c75b0b719865c96247b37407184bbfca5c353f2c2"}, + {file = "mypy-1.20.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:688c3312e5dadb573a2c69c82af3a298d43ecf9e6d264e0f95df960b5f6ac19c"}, + {file = "mypy-1.20.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29752dbbf8cc53f89f6ac096d363314333045c257c9c75cbd189ca2de0455744"}, + {file = "mypy-1.20.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:803203d2b6ea644982c644895c2f78b28d0e208bba7b27d9b921e0ec5eb207c6"}, + {file = "mypy-1.20.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9bcb8aa397ff0093c824182fd76a935a9ba7ad097fcbef80ae89bf6c1731d8ec"}, + {file = "mypy-1.20.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e061b58443f1736f8a37c48978d7ab581636d6ab03e3d4f99e3fa90463bb9382"}, + {file = "mypy-1.20.2-py3-none-any.whl", hash = "sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563"}, + {file = "mypy-1.20.2.tar.gz", hash = "sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665"}, +] + +[package.dependencies] +librt = {version = ">=0.8.0", markers = "platform_python_implementation != \"PyPy\""} +mypy_extensions = ">=1.0.0" +pathspec = ">=1.0.0" +typing_extensions = [ + {version = ">=4.6.0", markers = "python_version < \"3.15\""}, + {version = ">=4.14.0", markers = "python_version >= \"3.15\""}, +] + +[package.extras] +dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +native-parser = ["ast-serialize (>=0.1.1,<1.0.0)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + +[[package]] +name = "nodeenv" +version = "1.10.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +files = [ + {file = "nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827"}, + {file = "nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb"}, +] + +[[package]] +name = "packaging" +version = "26.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e"}, + {file = "packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661"}, +] + +[[package]] +name = "pastel" +version = "0.2.1" +description = "Bring colors to your terminal." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["dev"] +files = [ + {file = "pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364"}, + {file = "pastel-0.2.1.tar.gz", hash = "sha256:e6581ac04e973cac858828c6202c1e1e81fee1dc7de7683f3e1ffe0bfd8a573d"}, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189"}, + {file = "pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a"}, +] + +[package.extras] +hyperscan = ["hyperscan (>=0.7)"] +optional = ["typing-extensions (>=4)"] +re2 = ["google-re2 (>=1.1)"] + +[[package]] +name = "platformdirs" +version = "4.9.6" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917"}, + {file = "platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a"}, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "poethepoet" +version = "0.45.0" +description = "A task runner that works well with poetry and uv." +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "poethepoet-0.45.0-py3-none-any.whl", hash = "sha256:8e25f6e834ecf25fe2ddca676a4e0207eeb2e19def0a8709fc5c7f18e86cd68c"}, + {file = "poethepoet-0.45.0.tar.gz", hash = "sha256:cf2c2df4c185d33b619c2771de2b28c2b81c733f072226030c7fa4c273c040f8"}, +] + +[package.dependencies] +pastel = ">=0.2.1,<0.3.0" +pyyaml = ">=6.0.3,<7.0" + +[package.extras] +poetry-plugin = ["poetry (>=1.2.0,<3.0.0) ; python_version < \"4.0\""] + +[[package]] +name = "pre-commit" +version = "4.6.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b"}, + {file = "pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "pygments" +version = "2.20.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, + {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "9.0.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9"}, + {file = "pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c"}, +] + +[package.dependencies] +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +iniconfig = ">=1.0.1" +packaging = ">=22" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678"}, + {file = "pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2"}, +] + +[package.dependencies] +coverage = {version = ">=7.10.6", extras = ["toml"]} +pluggy = ">=1.2" +pytest = ">=7" + +[package.extras] +testing = ["process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-xdist" +version = "3.8.0" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88"}, + {file = "pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1"}, +] + +[package.dependencies] +execnet = ">=2.1" +pytest = ">=7.0.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-discovery" +version = "1.3.0" +description = "Python interpreter discovery" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "python_discovery-1.3.0-py3-none-any.whl", hash = "sha256:441d9ced3dfce36e113beb35ca302c71c7ef06f3c0f9c227a0b9bb3bd49b9e9f"}, + {file = "python_discovery-1.3.0.tar.gz", hash = "sha256:d098f1e86be5d45fe4d14bf1029294aabbd332f4321179dec85e76cddce834b0"}, +] + +[package.dependencies] +filelock = ">=3.15.4" +platformdirs = ">=4.3.6,<5" + +[package.extras] +docs = ["furo (>=2025.12.19)", "sphinx (>=9.1)", "sphinx-autodoc-typehints (>=3.6.3)", "sphinxcontrib-mermaid (>=2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.5.4)", "pytest (>=8.3.5)", "pytest-mock (>=3.14)", "setuptools (>=75.1)"] + +[[package]] +name = "pyyaml" +version = "6.0.3" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"}, + {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"}, + {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"}, + {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, + {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, + {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, + {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, + {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, + {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, + {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, + {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, + {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, + {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, +] + +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +description = "A custom YAML tag for referencing environment variables in YAML files." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"}, + {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"}, +] + +[package.dependencies] +pyyaml = "*" + +[[package]] +name = "ruff" +version = "0.15.12" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c"}, + {file = "ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c"}, + {file = "ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5"}, + {file = "ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002"}, + {file = "ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5"}, + {file = "ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6"}, + {file = "ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33"}, + {file = "ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847"}, + {file = "ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0"}, + {file = "ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339"}, + {file = "ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5"}, + {file = "ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd"}, + {file = "ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b"}, + {file = "ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e"}, + {file = "ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20"}, + {file = "ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d"}, + {file = "ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f"}, + {file = "ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6"}, +] + +[[package]] +name = "setuptools" +version = "78.1.1" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"}, + {file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["dev"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, +] + +[[package]] +name = "virtualenv" +version = "21.3.1" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "virtualenv-21.3.1-py3-none-any.whl", hash = "sha256:d1a71cf58f2f9228fff23a1f6ec15d39785c6b32e03658d104974247145edd35"}, + {file = "virtualenv-21.3.1.tar.gz", hash = "sha256:c2305bc1fddeec40699b8370d13f8d431b0701f00ce895061ce493aeded4426b"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = {version = ">=3.24.2,<4", markers = "python_version >= \"3.10\""} +platformdirs = ">=3.9.1,<5" +python-discovery = ">=1.2.2" + +[[package]] +name = "watchdog" +version = "6.0.0" +description = "Filesystem events monitoring" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, + {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, + {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, + {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, + {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[metadata] +lock-version = "2.1" +python-versions = ">=3.12" +content-hash = "aa734f52ed314d0af56c786d5eae67b0964d6d199359f642a10418b5cafd1866" diff --git a/pyproject.toml b/pyproject.toml index 387408eb5..a52491fd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,187 +1,187 @@ -[project] -name = "zxbasic" -version = "1.18.7" -description = "Boriel's ZX BASIC Compiler" -authors = [ - { name = "Jose Rodriguez", email = "zxbasic@boriel.com" } -] -license = "AGPL-3.0-or-later" -license-files = [ - "LICENSE.txt" -] -readme = "README.md" -requires-python = ">=3.12" -keywords = ["compiler", "zxspectrum", "BASIC", "z80"] -classifiers = [ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "Topic :: Software Development :: Build Tools", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", -] - -[project.urls] -Homepage = "https://github.com/boriel-basic" -Repository = "https://github.com/boriel-basic/zxbasic" -Documentation = "https://zxbasic.readthedocs.io" - -[project.scripts] -zxbc = "src.zxbc:main" -zxbasm = "src.zxbasm:main" -zxbpp = "src.zxbpp:entry_point" - -# Poetry-specific packaging configuration that is not part of PEP 621. -# Keep this minimal section to preserve the current package discovery behavior. -[tool.poetry] -packages = [ - { include = "src/**/*" } -] - -[tool.poetry.group.dev.dependencies] -pytest = "*" -bump2version = "^1.0.0" -pre-commit = "*" -mkdocs = "^1.2.2" -poethepoet = "*" -pytest-cov = "*" -ruff = "*" -mypy = "^1.8.0" -pytest-xdist = "*" -setuptools = ">=70.1.1,<79.0.0" - -[build-system] -requires = ["poetry-core>=2.4.1"] -build-backend = "poetry.core.masonry.api" - -[tool.poe.tasks] -[[tool.poe.tasks.lint]] -help = "Check code style and typing..." -shell = """ - ruff check . && - ruff format --check . && - mypy . - """ - -[[tool.poe.tasks.test]] -help = "Run tests" -shell = "pytest tests" - -[[tool.poe.tasks.test-no-cov]] -help = "Run tests faster (with no coverage)" -shell = "pytest tests --no-cov" - -[[tool.poe.tasks.format]] -help = "Formats code" -shell = """ - ruff check --fix - ruff check --select I --fix . - ruff format . - """ - -[[tool.poe.tasks.clean]] -help = "Clean up the project working tree (i.e. remove __pycache__ folders)" -shell = """ - find . -name "__pycache__" | xargs rm -rf - find . -name "*.pyc" -delete - rm -rf .mypy_cache .pytest_cache .ruff_cache dist/ .coverage.* - """ - -[tool.coverage.run] -branch = true -omit = [ - "tests/*", - "src/ply/*" -] - -[tool.pytest.ini_options] -minversion = "6.0" -norecursedirs = ["test_*tmp", "runtime"] -addopts = "--cov=src -n auto --dist=loadgroup --color=yes" - -[tool.mypy] -check_untyped_defs = true -disable_error_code = [ - "arg-type", - "assignment", - "attr-defined", - "call-overload", - "import-not-found", - "list-item", - "misc", - "name-defined", - "operator", - "union-attr", - "var-annotated", -] -exclude = [ - 'src/ply/.*\.py$', - 'scratch/*', - '.venv/*', - 'venv/*', -] - -[[tool.mypy.overrides]] -module = [ - "src.ply.*", -] -follow_imports = "skip" - -[tool.ruff] -line-length = 120 -target-version = "py314" -exclude = [ - ".venv/", - "venv/", - "src/ply" # PLY, external 3rd party tool -] - -[tool.ruff.lint] -select = [ - "C4", - "E", - "EXE", - "F", - "I", - "PLR", - "PLW", - "RET", - "RUF", - "UP", -] -ignore = [ - "E713", - "E722", - "E731", - "E741", - "EXE002", - "PLR0124", - "PLR0911", - "PLR0912", - "PLR0913", - "PLR0915", - "PLW1641", - "PLR1714", - "PLR1730", - "PLR2004", - "PLR5501", - "PLW0603", - "PLW2901", - "PLW0602", - "RET501", - "RET503", - "RET504", - "RET507", - "RUF005", - "RUF012", - "RUF013", - "RUF021", - "RUF022", # isort sorting style is not alphabetical and seems not natural either (despite the doc) - "UP008", - "UP015", # KEEP: redundant-open-modes - "UP024", - "UP028", - "UP030", - "UP031", -] +[project] +name = "zxbasic" +version = "1.18.7" +description = "Boriel's ZX BASIC Compiler" +authors = [ + { name = "Jose Rodriguez", email = "zxbasic@boriel.com" } +] +license = "AGPL-3.0-or-later" +license-files = [ + "LICENSE.txt" +] +readme = "README.md" +requires-python = ">=3.12" +keywords = ["compiler", "zxspectrum", "BASIC", "z80"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Topic :: Software Development :: Build Tools", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] + +[project.urls] +Homepage = "https://github.com/boriel-basic" +Repository = "https://github.com/boriel-basic/zxbasic" +Documentation = "https://zxbasic.readthedocs.io" + +[project.scripts] +zxbc = "src.zxbc:main" +zxbasm = "src.zxbasm:main" +zxbpp = "src.zxbpp:entry_point" + +# Poetry-specific packaging configuration that is not part of PEP 621. +# Keep this minimal section to preserve the current package discovery behavior. +[tool.poetry] +packages = [ + { include = "src/**/*" } +] + +[tool.poetry.group.dev.dependencies] +pytest = "*" +bump2version = "^1.0.0" +pre-commit = "*" +mkdocs = "^1.2.2" +poethepoet = "*" +pytest-cov = "*" +ruff = "*" +mypy = "^1.8.0" +pytest-xdist = "*" +setuptools = ">=70.1.1,<79.0.0" + +[build-system] +requires = ["poetry-core>=2.4.1"] +build-backend = "poetry.core.masonry.api" + +[tool.poe.tasks] +[[tool.poe.tasks.lint]] +help = "Check code style and typing..." +shell = """ + ruff check . && + ruff format --check . && + mypy . + """ + +[[tool.poe.tasks.test]] +help = "Run tests" +shell = "pytest tests" + +[[tool.poe.tasks.test-no-cov]] +help = "Run tests faster (with no coverage)" +shell = "pytest tests --no-cov" + +[[tool.poe.tasks.format]] +help = "Formats code" +shell = """ + ruff check --fix + ruff check --select I --fix . + ruff format . + """ + +[[tool.poe.tasks.clean]] +help = "Clean up the project working tree (i.e. remove __pycache__ folders)" +shell = """ + find . -name "__pycache__" | xargs rm -rf + find . -name "*.pyc" -delete + rm -rf .mypy_cache .pytest_cache .ruff_cache dist/ .coverage.* + """ + +[tool.coverage.run] +branch = true +omit = [ + "tests/*", + "src/ply/*" +] + +[tool.pytest.ini_options] +minversion = "6.0" +norecursedirs = ["test_*tmp", "runtime"] +addopts = "--cov=src -n auto --dist=loadgroup --color=yes" + +[tool.mypy] +check_untyped_defs = true +disable_error_code = [ + "arg-type", + "assignment", + "attr-defined", + "call-overload", + "import-not-found", + "list-item", + "misc", + "name-defined", + "operator", + "union-attr", + "var-annotated", +] +exclude = [ + 'src/ply/.*\.py$', + 'scratch/*', + '.venv/*', + 'venv/*', +] + +[[tool.mypy.overrides]] +module = [ + "src.ply.*", +] +follow_imports = "skip" + +[tool.ruff] +line-length = 120 +target-version = "py314" +exclude = [ + ".venv/", + "venv/", + "src/ply" # PLY, external 3rd party tool +] + +[tool.ruff.lint] +select = [ + "C4", + "E", + "EXE", + "F", + "I", + "PLR", + "PLW", + "RET", + "RUF", + "UP", +] +ignore = [ + "E713", + "E722", + "E731", + "E741", + "EXE002", + "PLR0124", + "PLR0911", + "PLR0912", + "PLR0913", + "PLR0915", + "PLW1641", + "PLR1714", + "PLR1730", + "PLR2004", + "PLR5501", + "PLW0603", + "PLW2901", + "PLW0602", + "RET501", + "RET503", + "RET504", + "RET507", + "RUF005", + "RUF012", + "RUF013", + "RUF021", + "RUF022", # isort sorting style is not alphabetical and seems not natural either (despite the doc) + "UP008", + "UP015", # KEEP: redundant-open-modes + "UP024", + "UP028", + "UP030", + "UP031", +] diff --git a/src/arch/z80/peephole/opts/000_o1_push_pop.opt b/src/arch/z80/peephole/opts/000_o1_push_pop.opt index ea9da4ffe..c1d370da5 100644 --- a/src/arch/z80/peephole/opts/000_o1_push_pop.opt +++ b/src/arch/z80/peephole/opts/000_o1_push_pop.opt @@ -1,14 +1,14 @@ -;; Remove sequence: -;; push xx -;; pop xx - -OLEVEL: 1 -OFLAG: 1 - -REPLACE {{ - push $1 - pop $1 -}} - -WITH {{ -}} +;; Remove sequence: +;; push xx +;; pop xx + +OLEVEL: 1 +OFLAG: 1 + +REPLACE {{ + push $1 + pop $1 +}} + +WITH {{ +}} diff --git a/src/arch/z80/peephole/opts/000_o3_push_pop.opt b/src/arch/z80/peephole/opts/000_o3_push_pop.opt index 23435c3ad..ed00d9c15 100644 --- a/src/arch/z80/peephole/opts/000_o3_push_pop.opt +++ b/src/arch/z80/peephole/opts/000_o3_push_pop.opt @@ -1,14 +1,14 @@ -;; Remove sequence: -;; push xx -;; pop xx - -OLEVEL: 3 -OFLAG: 1 - -REPLACE {{ - push $1 - pop $1 -}} - -WITH {{ -}} +;; Remove sequence: +;; push xx +;; pop xx + +OLEVEL: 3 +OFLAG: 1 + +REPLACE {{ + push $1 + pop $1 +}} + +WITH {{ +}} diff --git a/src/arch/z80/peephole/opts/001_o1_ld_XXYY_ldYYXX.opt b/src/arch/z80/peephole/opts/001_o1_ld_XXYY_ldYYXX.opt index 52e8bd562..895da86c7 100644 --- a/src/arch/z80/peephole/opts/001_o1_ld_XXYY_ldYYXX.opt +++ b/src/arch/z80/peephole/opts/001_o1_ld_XXYY_ldYYXX.opt @@ -1,18 +1,18 @@ -;; Replaces sequence: -;; ld XX, YY -;; ld YY, XX ;; This is useless - -;; With: -;; ld XX, YY - -OLEVEL: 1 -OFLAG: 2 - -REPLACE {{ - ld $1, $2 - ld $2, $1 -}} - -WITH {{ - ld $1, $2 -}} +;; Replaces sequence: +;; ld XX, YY +;; ld YY, XX ;; This is useless + +;; With: +;; ld XX, YY + +OLEVEL: 1 +OFLAG: 2 + +REPLACE {{ + ld $1, $2 + ld $2, $1 +}} + +WITH {{ + ld $1, $2 +}} diff --git a/src/arch/z80/peephole/opts/002_o1_push_hlde_to_ex.opt b/src/arch/z80/peephole/opts/002_o1_push_hlde_to_ex.opt index 0c491ad92..3964d8415 100644 --- a/src/arch/z80/peephole/opts/002_o1_push_hlde_to_ex.opt +++ b/src/arch/z80/peephole/opts/002_o1_push_hlde_to_ex.opt @@ -1,26 +1,26 @@ -;; Replaces sequence: -;; push de -;; push hl -;; pop de -;; pop hl - -;; with -;; ex de, hl - -OLEVEL: 1 -OFLAG: 3 - -REPLACE {{ - push $1 - push $2 - pop $1 - pop $2 -}} - -IF {{ - (($1 == hl) && ($2 == de)) || (($1 == de) && ($2 == hl)) -}} - -WITH {{ - ex de, hl +;; Replaces sequence: +;; push de +;; push hl +;; pop de +;; pop hl + +;; with +;; ex de, hl + +OLEVEL: 1 +OFLAG: 3 + +REPLACE {{ + push $1 + push $2 + pop $1 + pop $2 +}} + +IF {{ + (($1 == hl) && ($2 == de)) || (($1 == de) && ($2 == hl)) +}} + +WITH {{ + ex de, hl }} \ No newline at end of file diff --git a/src/arch/z80/peephole/opts/003_o1_useless_jp.opt b/src/arch/z80/peephole/opts/003_o1_useless_jp.opt index ab9eadddb..2e3131fb7 100644 --- a/src/arch/z80/peephole/opts/003_o1_useless_jp.opt +++ b/src/arch/z80/peephole/opts/003_o1_useless_jp.opt @@ -1,18 +1,18 @@ -;; Replaces sequence: -;; jp XXX -;; XXX: -;; with -;; XXX: -;; (jp is useless here) - -OLEVEL: 1 -OFLAG: 4 - -REPLACE {{ - jp$1 $2 - $2: -}} - -WITH {{ - $2: +;; Replaces sequence: +;; jp XXX +;; XXX: +;; with +;; XXX: +;; (jp is useless here) + +OLEVEL: 1 +OFLAG: 4 + +REPLACE {{ + jp$1 $2 + $2: +}} + +WITH {{ + $2: }} \ No newline at end of file diff --git a/src/arch/z80/peephole/opts/004_o1_push_pop_ldld.opt b/src/arch/z80/peephole/opts/004_o1_push_pop_ldld.opt index a1f78caed..85b149097 100644 --- a/src/arch/z80/peephole/opts/004_o1_push_pop_ldld.opt +++ b/src/arch/z80/peephole/opts/004_o1_push_pop_ldld.opt @@ -1,27 +1,27 @@ -;; Replaces sequence: -;; push hl -;; pop de -;; pop hl | ld hl, XX -;; with -;; ex de, hl -;; pop hl | ld hl, XX -;; (also reverse order allowed) - -OLEVEL: 1 -OFLAG: 5 - -REPLACE {{ - ld d, h - ld e, l - $1 hl$2 -}} - -IF {{ - ($1 == pop) || ($1 == ld) -}} - -WITH {{ - ex de, hl - $1 hl$2 -}} - +;; Replaces sequence: +;; push hl +;; pop de +;; pop hl | ld hl, XX +;; with +;; ex de, hl +;; pop hl | ld hl, XX +;; (also reverse order allowed) + +OLEVEL: 1 +OFLAG: 5 + +REPLACE {{ + ld d, h + ld e, l + $1 hl$2 +}} + +IF {{ + ($1 == pop) || ($1 == ld) +}} + +WITH {{ + ex de, hl + $1 hl$2 +}} + diff --git a/src/arch/z80/peephole/opts/005_o1_push_af_ld_aX.opt b/src/arch/z80/peephole/opts/005_o1_push_af_ld_aX.opt index 484cf5945..cfc19bd71 100644 --- a/src/arch/z80/peephole/opts/005_o1_push_af_ld_aX.opt +++ b/src/arch/z80/peephole/opts/005_o1_push_af_ld_aX.opt @@ -1,23 +1,23 @@ -;; Replaces sequence: -;; ld a, (ix|iy +|- NNN) -;; inc|dec|cp a -;; ld (ix|iy +|- NNN), a -;; with -;; inc|dec|cp (ix|iy +|- NNN) - -OLEVEL: 1 -OFLAG: 6 - -REPLACE {{ - ld a, $1 - $2 a - ld $1, a -}} - -IF {{ - (IS_INDIR($1) || ($1 == "(hl)")) && (($2 == cp) || ($2 == inc) || ($2 == dec)) -}} - -WITH {{ - $2 $1 -}} +;; Replaces sequence: +;; ld a, (ix|iy +|- NNN) +;; inc|dec|cp a +;; ld (ix|iy +|- NNN), a +;; with +;; inc|dec|cp (ix|iy +|- NNN) + +OLEVEL: 1 +OFLAG: 6 + +REPLACE {{ + ld a, $1 + $2 a + ld $1, a +}} + +IF {{ + (IS_INDIR($1) || ($1 == "(hl)")) && (($2 == cp) || ($2 == inc) || ($2 == dec)) +}} + +WITH {{ + $2 $1 +}} diff --git a/src/arch/z80/peephole/opts/006_o1_push_af_pop_XX.opt b/src/arch/z80/peephole/opts/006_o1_push_af_pop_XX.opt index a28692209..f85f844fd 100644 --- a/src/arch/z80/peephole/opts/006_o1_push_af_pop_XX.opt +++ b/src/arch/z80/peephole/opts/006_o1_push_af_pop_XX.opt @@ -1,27 +1,27 @@ -;; Replaces sequence: -;; push af -;; pop XY -;; with -;; ld X, a -;; (also reverse order allowed) - -OLEVEL: 1 -OFLAG: 7 - -REPLACE {{ - push $1 - pop $2 -}} - -IF {{ - ($1 <> $2) && (($1 == af) || ($2 == af)) -}} - -DEFINE {{ - $3 = HIREG($1) - $4 = HIREG($2) -}} - -WITH {{ - ld $4, $3 -}} +;; Replaces sequence: +;; push af +;; pop XY +;; with +;; ld X, a +;; (also reverse order allowed) + +OLEVEL: 1 +OFLAG: 7 + +REPLACE {{ + push $1 + pop $2 +}} + +IF {{ + ($1 <> $2) && (($1 == af) || ($2 == af)) +}} + +DEFINE {{ + $3 = HIREG($1) + $4 = HIREG($2) +}} + +WITH {{ + ld $4, $3 +}} diff --git a/src/arch/z80/peephole/opts/006_o3_push_af_pop_XX.opt b/src/arch/z80/peephole/opts/006_o3_push_af_pop_XX.opt index 6b2cde034..7ead1848b 100644 --- a/src/arch/z80/peephole/opts/006_o3_push_af_pop_XX.opt +++ b/src/arch/z80/peephole/opts/006_o3_push_af_pop_XX.opt @@ -1,27 +1,27 @@ -;; Replaces sequence: -;; push af -;; pop XY -;; with -;; ld X, a -;; (also reverse order allowed) - -OLEVEL: 3 -OFLAG: 7 - -REPLACE {{ - push $1 - pop $2 -}} - -IF {{ - ($1 <> $2) && (($1 == af) || ($2 == af)) -}} - -DEFINE {{ - $3 = HIREG($1) - $4 = HIREG($2) -}} - -WITH {{ - ld $4, $3 -}} +;; Replaces sequence: +;; push af +;; pop XY +;; with +;; ld X, a +;; (also reverse order allowed) + +OLEVEL: 3 +OFLAG: 7 + +REPLACE {{ + push $1 + pop $2 +}} + +IF {{ + ($1 <> $2) && (($1 == af) || ($2 == af)) +}} + +DEFINE {{ + $3 = HIREG($1) + $4 = HIREG($2) +}} + +WITH {{ + ld $4, $3 +}} diff --git a/src/arch/z80/peephole/opts/007_o1_ex_de_hl.opt b/src/arch/z80/peephole/opts/007_o1_ex_de_hl.opt index 64bfd7aec..d0494bc02 100644 --- a/src/arch/z80/peephole/opts/007_o1_ex_de_hl.opt +++ b/src/arch/z80/peephole/opts/007_o1_ex_de_hl.opt @@ -1,16 +1,16 @@ -;; Remove sequence: -;; ex de, hl -;; ex de, hl -;; -;; Also works with ex af, af' and exx - -OLEVEL: 1 -OFLAG: 8 - -REPLACE {{ - ex$1 - ex$1 -}} - -WITH {{ -}} +;; Remove sequence: +;; ex de, hl +;; ex de, hl +;; +;; Also works with ex af, af' and exx + +OLEVEL: 1 +OFLAG: 8 + +REPLACE {{ + ex$1 + ex$1 +}} + +WITH {{ +}} diff --git a/src/arch/z80/peephole/opts/008_o1_sbc_jp.opt b/src/arch/z80/peephole/opts/008_o1_sbc_jp.opt index c3ed14eaf..2af5c2b95 100644 --- a/src/arch/z80/peephole/opts/008_o1_sbc_jp.opt +++ b/src/arch/z80/peephole/opts/008_o1_sbc_jp.opt @@ -1,28 +1,28 @@ -;; Replace sequence: -;; sbc a, a -;; or a -;; jp z, __X -;; -;; With: -;; jp nc, __X - -OLEVEL: 1 -OFLAG: 9 - -REPLACE {{ - sbc a, a - or a - $1 $2, $3 -}} - -DEFINE {{ - $4 = (($2 == "z") && "nc") || ("c") -}} - -IF {{ - (($1 == "jp") || ($1 == "jr")) -}} - -WITH {{ - $1 $4, $3 -}} +;; Replace sequence: +;; sbc a, a +;; or a +;; jp z, __X +;; +;; With: +;; jp nc, __X + +OLEVEL: 1 +OFLAG: 9 + +REPLACE {{ + sbc a, a + or a + $1 $2, $3 +}} + +DEFINE {{ + $4 = (($2 == "z") && "nc") || ("c") +}} + +IF {{ + (($1 == "jp") || ($1 == "jr")) +}} + +WITH {{ + $1 $4, $3 +}} diff --git a/src/arch/z80/peephole/opts/009_o1_inc_mem.opt b/src/arch/z80/peephole/opts/009_o1_inc_mem.opt index a49d185e6..fbeee997a 100644 --- a/src/arch/z80/peephole/opts/009_o1_inc_mem.opt +++ b/src/arch/z80/peephole/opts/009_o1_inc_mem.opt @@ -1,30 +1,30 @@ -;; Replace sequence: -;; ld a, (xxx) -;; inc a | dec a -;; ld (xxx), a -;; -;; With: -;; ld hl, xxx -;; inc (hl) | dec (hl) - -OLEVEL: 1 -OFLAG: 10 - -REPLACE {{ - ld a, ($1) - $2 a - ld ($1), a -}} - -DEFINE {{ - $3 = "(" + $1 + ")" -}} - -IF {{ - (($2 == "inc") || ($2 == "dec")) && (($1 <> "hl") && (!IS_INDIR($3))) -}} - -WITH {{ - ld hl, $1 - $2 (hl) -}} +;; Replace sequence: +;; ld a, (xxx) +;; inc a | dec a +;; ld (xxx), a +;; +;; With: +;; ld hl, xxx +;; inc (hl) | dec (hl) + +OLEVEL: 1 +OFLAG: 10 + +REPLACE {{ + ld a, ($1) + $2 a + ld ($1), a +}} + +DEFINE {{ + $3 = "(" + $1 + ")" +}} + +IF {{ + (($2 == "inc") || ($2 == "dec")) && (($1 <> "hl") && (!IS_INDIR($3))) +}} + +WITH {{ + ld hl, $1 + $2 (hl) +}} diff --git a/src/arch/z80/peephole/opts/010_o1_ld_de_hl_ex_de_hl.opt b/src/arch/z80/peephole/opts/010_o1_ld_de_hl_ex_de_hl.opt index 4f29f3eac..efc9def61 100644 --- a/src/arch/z80/peephole/opts/010_o1_ld_de_hl_ex_de_hl.opt +++ b/src/arch/z80/peephole/opts/010_o1_ld_de_hl_ex_de_hl.opt @@ -1,33 +1,33 @@ -;; Replaces sequence: -;; push AB -;; pop CD -;; With: -;; ld C, A -;; ld B, D - -OLEVEL: 1 -OFLAG: 11 - -REPLACE {{ - push $1 - pop $2 -}} - -DEFINE {{ - $3 = HIREG($1) - $4 = LOREG($1) - $5 = HIREG($2) - $6 = LOREG($2) -}} - -IF {{ - ($1 <> $2) && - (($1 == hl) || ($1 == de) || ($1 == bc)) && - (($2 == hl) || ($2 == de) || ($2 == bc)) -}} - -WITH {{ - ld $5, $3 - ld $6, $4 -}} - +;; Replaces sequence: +;; push AB +;; pop CD +;; With: +;; ld C, A +;; ld B, D + +OLEVEL: 1 +OFLAG: 11 + +REPLACE {{ + push $1 + pop $2 +}} + +DEFINE {{ + $3 = HIREG($1) + $4 = LOREG($1) + $5 = HIREG($2) + $6 = LOREG($2) +}} + +IF {{ + ($1 <> $2) && + (($1 == hl) || ($1 == de) || ($1 == bc)) && + (($2 == hl) || ($2 == de) || ($2 == bc)) +}} + +WITH {{ + ld $5, $3 + ld $6, $4 +}} + diff --git a/src/arch/z80/peephole/opts/011_o1_ld_h_a_pop_af_or_h.opt b/src/arch/z80/peephole/opts/011_o1_ld_h_a_pop_af_or_h.opt index 85c8e810d..ea8f660c6 100644 --- a/src/arch/z80/peephole/opts/011_o1_ld_h_a_pop_af_or_h.opt +++ b/src/arch/z80/peephole/opts/011_o1_ld_h_a_pop_af_or_h.opt @@ -1,27 +1,27 @@ -;; Replaces sequence: -;; ld h, a -;; pop af -;; or h | and h | xor h -;; With: -;; pop de -;; or d | and d | xor d -;; -;; At O1 Level, this happens only with 8 bit operations between h and a - -OLEVEL: 1 -OFLAG: 12 - -REPLACE {{ - ld h, a - pop af - $1 h -}} - -IF {{ - ($1 IN (or, and, xor)) -}} - -WITH {{ - pop de - $1 d -}} +;; Replaces sequence: +;; ld h, a +;; pop af +;; or h | and h | xor h +;; With: +;; pop de +;; or d | and d | xor d +;; +;; At O1 Level, this happens only with 8 bit operations between h and a + +OLEVEL: 1 +OFLAG: 12 + +REPLACE {{ + ld h, a + pop af + $1 h +}} + +IF {{ + ($1 IN (or, and, xor)) +}} + +WITH {{ + pop de + $1 d +}} diff --git a/src/arch/z80/peephole/opts/012_o1_ld_hl_push_pop_de.opt b/src/arch/z80/peephole/opts/012_o1_ld_hl_push_pop_de.opt index 5b5722ff0..5b438789a 100644 --- a/src/arch/z80/peephole/opts/012_o1_ld_hl_push_pop_de.opt +++ b/src/arch/z80/peephole/opts/012_o1_ld_hl_push_pop_de.opt @@ -1,27 +1,27 @@ -;; Replaces sequence: -;; ld hl, XX -;; push hl -;; ld hl, YY -;; pop de -;; with -;; ld de, XX -;; ld hl, YY - -OLEVEL: 1 -OFLAG: 13 - -REPLACE {{ - ld hl, $1 - push hl - ld hl, $2 - pop de -}} - -IF {{ -}} - -WITH {{ - ld de, $1 - ld hl, $2 -}} - +;; Replaces sequence: +;; ld hl, XX +;; push hl +;; ld hl, YY +;; pop de +;; with +;; ld de, XX +;; ld hl, YY + +OLEVEL: 1 +OFLAG: 13 + +REPLACE {{ + ld hl, $1 + push hl + ld hl, $2 + pop de +}} + +IF {{ +}} + +WITH {{ + ld de, $1 + ld hl, $2 +}} + diff --git a/src/arch/z80/peephole/opts/013_o1_neg_neg.opt b/src/arch/z80/peephole/opts/013_o1_neg_neg.opt index 6c625562a..058ed0cf0 100644 --- a/src/arch/z80/peephole/opts/013_o1_neg_neg.opt +++ b/src/arch/z80/peephole/opts/013_o1_neg_neg.opt @@ -1,16 +1,16 @@ -;; Replaces sequence: -;; neg -;; neg -;; with -;; ; nop ;; double neg can be removed - -OLEVEL: 1 -OFLAG: 13 - -REPLACE {{ - neg - neg -}} - -WITH {{ -}} +;; Replaces sequence: +;; neg +;; neg +;; with +;; ; nop ;; double neg can be removed + +OLEVEL: 1 +OFLAG: 13 + +REPLACE {{ + neg + neg +}} + +WITH {{ +}} diff --git a/src/arch/z80/peephole/opts/014_o1_or_sbc_a_a.opt b/src/arch/z80/peephole/opts/014_o1_or_sbc_a_a.opt index 26954a515..97ee62f05 100644 --- a/src/arch/z80/peephole/opts/014_o1_or_sbc_a_a.opt +++ b/src/arch/z80/peephole/opts/014_o1_or_sbc_a_a.opt @@ -1,22 +1,22 @@ -;; Replaces sequence: -;; or|xor|and a -;; sbc a, a -;; with -;; xor a - -OLEVEL: 1 -OFLAG: 14 - -REPLACE {{ - $1 a - sbc a, a -}} - -IF {{ - ($1 IN (or, and, xor)) -}} - -WITH {{ - xor a -}} - +;; Replaces sequence: +;; or|xor|and a +;; sbc a, a +;; with +;; xor a + +OLEVEL: 1 +OFLAG: 14 + +REPLACE {{ + $1 a + sbc a, a +}} + +IF {{ + ($1 IN (or, and, xor)) +}} + +WITH {{ + xor a +}} + diff --git a/src/arch/z80/peephole/opts/015_o3_ld_r_n_not_required.opt b/src/arch/z80/peephole/opts/015_o3_ld_r_n_not_required.opt index aa814c363..839a9aa5c 100644 --- a/src/arch/z80/peephole/opts/015_o3_ld_r_n_not_required.opt +++ b/src/arch/z80/peephole/opts/015_o3_ld_r_n_not_required.opt @@ -1,18 +1,18 @@ -;; Removes sequence -;; ld a, XXXX -;; if a is not used later - -OLEVEL: 3 -OFLAG: 15 - -REPLACE {{ - ld a, $1 -}} - -IF {{ - !IS_REQUIRED(a) -}} - -WITH {{ -}} - +;; Removes sequence +;; ld a, XXXX +;; if a is not used later + +OLEVEL: 3 +OFLAG: 15 + +REPLACE {{ + ld a, $1 +}} + +IF {{ + !IS_REQUIRED(a) +}} + +WITH {{ +}} + diff --git a/src/arch/z80/peephole/opts/016_o1_xor_a_jp_z.opt b/src/arch/z80/peephole/opts/016_o1_xor_a_jp_z.opt index 66315a3da..6b6dd4593 100644 --- a/src/arch/z80/peephole/opts/016_o1_xor_a_jp_z.opt +++ b/src/arch/z80/peephole/opts/016_o1_xor_a_jp_z.opt @@ -1,22 +1,22 @@ -;; Replaces sequence: -;; or|xor|and a -;; sbc a, a -;; with -;; xor a - -OLEVEL: 1 -OFLAG: 17 - -REPLACE {{ - xor a - $1 z, $2 -}} - -IF {{ - ($1 IN (jp, jr)) -}} - -WITH {{ - $1 $2 -}} - +;; Replaces sequence: +;; or|xor|and a +;; sbc a, a +;; with +;; xor a + +OLEVEL: 1 +OFLAG: 17 + +REPLACE {{ + xor a + $1 z, $2 +}} + +IF {{ + ($1 IN (jp, jr)) +}} + +WITH {{ + $1 $2 +}} + diff --git a/src/arch/z80/peephole/opts/017_jp_cond_jp_labell.opt b/src/arch/z80/peephole/opts/017_jp_cond_jp_labell.opt index ad275466b..9a2bbeb17 100644 --- a/src/arch/z80/peephole/opts/017_jp_cond_jp_labell.opt +++ b/src/arch/z80/peephole/opts/017_jp_cond_jp_labell.opt @@ -1,30 +1,30 @@ -;; Replace sequence: -;; jp , __LABEL -;; jp OTHER -;; __LABEL: -;; -;; With: -;; jp !, OTHER - -OLEVEL: 1 -OFLAG: 17 - -REPLACE {{ - $1 $2, $3 - $4 $5 -$3: -}} - -;; Defines $4 as the negated condition -DEFINE {{ - $6 = (($2 == nc) && c) || (($2 == c) && nc) || (($2 == nz) && z) || (($2 == z) && nz) -}} - -IF {{ - $6 && (LEN($5) == 1) && (($1 == jp) || ($1 == jr)) && (($4 == jp) || ($4 == jr)) -}} - -WITH {{ - $4 $6, $5 -$3: -}} +;; Replace sequence: +;; jp , __LABEL +;; jp OTHER +;; __LABEL: +;; +;; With: +;; jp !, OTHER + +OLEVEL: 1 +OFLAG: 17 + +REPLACE {{ + $1 $2, $3 + $4 $5 +$3: +}} + +;; Defines $4 as the negated condition +DEFINE {{ + $6 = (($2 == nc) && c) || (($2 == c) && nc) || (($2 == nz) && z) || (($2 == z) && nz) +}} + +IF {{ + $6 && (LEN($5) == 1) && (($1 == jp) || ($1 == jr)) && (($4 == jp) || ($4 == jr)) +}} + +WITH {{ + $4 $6, $5 +$3: +}} diff --git a/src/arch/z80/peephole/opts/018_EQ16_by_sbchl.opt b/src/arch/z80/peephole/opts/018_EQ16_by_sbchl.opt index 00afeaca2..e2008e5ff 100644 --- a/src/arch/z80/peephole/opts/018_EQ16_by_sbchl.opt +++ b/src/arch/z80/peephole/opts/018_EQ16_by_sbchl.opt @@ -1,33 +1,33 @@ -;; Tries to optimize a == b for U/Integers -;; Replace sequence: -;; call __EQ16 -;; or a | and a -;; jp nz, ... -;; With: -;; or a -;; sbc hl, de -;; jp z, ... - -OLEVEL: 1 -OFLAG: 18 - -REPLACE {{ - call __EQ16 - $1 a - jp $2, $3 -}} - -;; Defines $4 as the negated condition -DEFINE {{ - $4 = (($2 == nz) && z) || nz -}} - -IF {{ - ($1 == or) || ($1 == and) -}} - -WITH {{ - or a - sbc hl, de - jp $4, $3 -}} +;; Tries to optimize a == b for U/Integers +;; Replace sequence: +;; call __EQ16 +;; or a | and a +;; jp nz, ... +;; With: +;; or a +;; sbc hl, de +;; jp z, ... + +OLEVEL: 1 +OFLAG: 18 + +REPLACE {{ + call __EQ16 + $1 a + jp $2, $3 +}} + +;; Defines $4 as the negated condition +DEFINE {{ + $4 = (($2 == nz) && z) || nz +}} + +IF {{ + ($1 == or) || ($1 == and) +}} + +WITH {{ + or a + sbc hl, de + jp $4, $3 +}} diff --git a/src/arch/z80/peephole/opts/019_sub1_jpnc_ora_jpz.opt b/src/arch/z80/peephole/opts/019_sub1_jpnc_ora_jpz.opt index cae3a6e4a..b7348a713 100644 --- a/src/arch/z80/peephole/opts/019_sub1_jpnc_ora_jpz.opt +++ b/src/arch/z80/peephole/opts/019_sub1_jpnc_ora_jpz.opt @@ -1,28 +1,28 @@ -;; Tries to optimize a == b for U/Bytes -;; Replace sequence: -;; sub 1 -;; jp nc, __LABEL | jp c, __LABEL -;; With: -;; or a -;; jp nz, __LABEL | jp z, __LABEL - -OLEVEL: 1 -OFLAG: 19 - -REPLACE {{ - sub 1 - $1 $2, $3 -}} - -DEFINE {{ - $4 = (($2 == nc) && nz) || z -}} - -IF {{ - (($1 == jp) || ($1 == jr)) && (($2 == nc) || ($2 == c)) -}} - -WITH {{ - or a - $1 $4, $3 -}} +;; Tries to optimize a == b for U/Bytes +;; Replace sequence: +;; sub 1 +;; jp nc, __LABEL | jp c, __LABEL +;; With: +;; or a +;; jp nz, __LABEL | jp z, __LABEL + +OLEVEL: 1 +OFLAG: 19 + +REPLACE {{ + sub 1 + $1 $2, $3 +}} + +DEFINE {{ + $4 = (($2 == nc) && nz) || z +}} + +IF {{ + (($1 == jp) || ($1 == jr)) && (($2 == nc) || ($2 == c)) +}} + +WITH {{ + or a + $1 $4, $3 +}} diff --git a/src/arch/z80/peephole/opts/020_o1_bool_norm_empty.opt b/src/arch/z80/peephole/opts/020_o1_bool_norm_empty.opt index 07acaf1ae..e10815c07 100644 --- a/src/arch/z80/peephole/opts/020_o1_bool_norm_empty.opt +++ b/src/arch/z80/peephole/opts/020_o1_bool_norm_empty.opt @@ -1,32 +1,32 @@ -;; Remove the boolean normalization if it's done after calling -;; certain routines that return the bool result already normalized. - -;; The sequence -;; sub 1 -;; sbc a, a -;; inc a -;; can be removed - -OLEVEL: 1 -OFLAG: 20 - -REPLACE {{ - $1 - sub 1 - sbc a, a - inc a -}} - -WITH {{ - $1 -}} - -IF {{ - $1 IN ("xor a", "ld a, 0", - "call .core.__GEF", "call .core.__LEI16", "call .core.__LEI8", "call .core.__LTI8", - "call .core.__ANDF", "call .core.__EQF", "call .core.__GTF", "call .core.__LTI16", - "call .core.__LEF", "call .core.__LEI32", "call .core.__LTF", "call .core.__LTI32", - "call .core.__NEF", "call .core.__NOTF", "call .core.__ORF", "call .core.__XORF", - "call .core.__STREQ", "call .core.__STRNE", "call .core.__STRLT", "call .core.__STRLE", - "call .core.__STRGT", "call .core.__STRGE") -}} +;; Remove the boolean normalization if it's done after calling +;; certain routines that return the bool result already normalized. + +;; The sequence +;; sub 1 +;; sbc a, a +;; inc a +;; can be removed + +OLEVEL: 1 +OFLAG: 20 + +REPLACE {{ + $1 + sub 1 + sbc a, a + inc a +}} + +WITH {{ + $1 +}} + +IF {{ + $1 IN ("xor a", "ld a, 0", + "call .core.__GEF", "call .core.__LEI16", "call .core.__LEI8", "call .core.__LTI8", + "call .core.__ANDF", "call .core.__EQF", "call .core.__GTF", "call .core.__LTI16", + "call .core.__LEF", "call .core.__LEI32", "call .core.__LTF", "call .core.__LTI32", + "call .core.__NEF", "call .core.__NOTF", "call .core.__ORF", "call .core.__XORF", + "call .core.__STREQ", "call .core.__STRNE", "call .core.__STRLT", "call .core.__STRLE", + "call .core.__STRGT", "call .core.__STRGE") +}} diff --git a/src/arch/z80/peephole/opts/021_o1_bool_norm_neg.opt b/src/arch/z80/peephole/opts/021_o1_bool_norm_neg.opt index dc7f71d37..278bf3539 100644 --- a/src/arch/z80/peephole/opts/021_o1_bool_norm_neg.opt +++ b/src/arch/z80/peephole/opts/021_o1_bool_norm_neg.opt @@ -1,26 +1,26 @@ -;; The sequence: -;; sbc a, a ; A is either 0 or -1 -;; sub 1 -;; sbc a, a -;; inc a -;; can be replaced by -;; neg ; A is either 0 or 1 - -OLEVEL: 1 -OFLAG: 21 - -REPLACE {{ - $1 - sub 1 - sbc a, a - inc a -}} - -WITH {{ - $1 - neg -}} - -IF {{ - $1 IN ("sbc a, a", "call .core.__NOT32", "call .core.__XOR8", "call .core.__XOR16", "call .core.__XOR32") -}} +;; The sequence: +;; sbc a, a ; A is either 0 or -1 +;; sub 1 +;; sbc a, a +;; inc a +;; can be replaced by +;; neg ; A is either 0 or 1 + +OLEVEL: 1 +OFLAG: 21 + +REPLACE {{ + $1 + sub 1 + sbc a, a + inc a +}} + +WITH {{ + $1 + neg +}} + +IF {{ + $1 IN ("sbc a, a", "call .core.__NOT32", "call .core.__XOR8", "call .core.__XOR16", "call .core.__XOR32") +}} diff --git a/src/arch/z80/peephole/opts/022_insr_a_or_a.opt b/src/arch/z80/peephole/opts/022_insr_a_or_a.opt index 406296d01..fb3c21ac6 100644 --- a/src/arch/z80/peephole/opts/022_insr_a_or_a.opt +++ b/src/arch/z80/peephole/opts/022_insr_a_or_a.opt @@ -1,24 +1,24 @@ -;; Replace sequence: -;; and X|or X|xor X|sub X|cp X|dec a|inc a -;; or a -;; -;; With: -;; and X|or X|xor X|sub X|cp X|dec a|inc a - -OLEVEL: 1 -OFLAG: 22 - -REPLACE {{ - $1 $2 - $3 a -}} - -IF {{ - (($3 == or) || ($3 == and)) && - (($1 == or) || ($1 == sub) || ($1 == and) || ($1 == xor) || ($1 == cp) || - (($2 == a) && (($1 == dec) || ($1 == inc)))) -}} - -WITH {{ - $1 $2 -}} +;; Replace sequence: +;; and X|or X|xor X|sub X|cp X|dec a|inc a +;; or a +;; +;; With: +;; and X|or X|xor X|sub X|cp X|dec a|inc a + +OLEVEL: 1 +OFLAG: 22 + +REPLACE {{ + $1 $2 + $3 a +}} + +IF {{ + (($3 == or) || ($3 == and)) && + (($1 == or) || ($1 == sub) || ($1 == and) || ($1 == xor) || ($1 == cp) || + (($2 == a) && (($1 == dec) || ($1 == inc)))) +}} + +WITH {{ + $1 $2 +}} diff --git a/src/arch/z80/peephole/opts/023_ld_hl_bc_outl.opt b/src/arch/z80/peephole/opts/023_ld_hl_bc_outl.opt index 617a2ae4b..8a863b248 100644 --- a/src/arch/z80/peephole/opts/023_ld_hl_bc_outl.opt +++ b/src/arch/z80/peephole/opts/023_ld_hl_bc_outl.opt @@ -1,27 +1,27 @@ -;; Replace sequence: -;; ld hl, (NN) | ld hl, NN | pop hl -;; ld b, h -;; ld c, l -;; out (c), a | in a, (c) -;; With: -;; ld bc, (NN) | ld bc, NN | pop bc -;; out (c), a | in a, (c) - -OLEVEL: 1 -OFLAG: 23 - -REPLACE {{ - $1 hl$2 - ld b, h - ld c, l - $3 -}} - -IF {{ - (($1 == ld) || ($1 == pop)) && (($3 == "out (c), a") || ($3 == "in a, (c)")) -}} - -WITH {{ - $1 bc$2 - $3 -}} +;; Replace sequence: +;; ld hl, (NN) | ld hl, NN | pop hl +;; ld b, h +;; ld c, l +;; out (c), a | in a, (c) +;; With: +;; ld bc, (NN) | ld bc, NN | pop bc +;; out (c), a | in a, (c) + +OLEVEL: 1 +OFLAG: 23 + +REPLACE {{ + $1 hl$2 + ld b, h + ld c, l + $3 +}} + +IF {{ + (($1 == ld) || ($1 == pop)) && (($3 == "out (c), a") || ($3 == "in a, (c)")) +}} + +WITH {{ + $1 bc$2 + $3 +}} diff --git a/src/arch/z80/peephole/opts/024_o1_bool_norm_duble_neg.opt b/src/arch/z80/peephole/opts/024_o1_bool_norm_duble_neg.opt index 315ec4ea2..c3fb6a345 100644 --- a/src/arch/z80/peephole/opts/024_o1_bool_norm_duble_neg.opt +++ b/src/arch/z80/peephole/opts/024_o1_bool_norm_duble_neg.opt @@ -1,23 +1,23 @@ -;; The sequence: -;; sbc a, a ; A is either 0 or -1 -;; sub 1 -;; sbc a, a -;; neg -;; can be replaced by -;; sbc a, a ; A is either 0 or -1 -;; inc a - -OLEVEL: 1 -OFLAG: 24 - -REPLACE {{ - sbc a, a - sub 1 - sbc a, a - neg -}} - -WITH {{ - sbc a, a - inc a -}} +;; The sequence: +;; sbc a, a ; A is either 0 or -1 +;; sub 1 +;; sbc a, a +;; neg +;; can be replaced by +;; sbc a, a ; A is either 0 or -1 +;; inc a + +OLEVEL: 1 +OFLAG: 24 + +REPLACE {{ + sbc a, a + sub 1 + sbc a, a + neg +}} + +WITH {{ + sbc a, a + inc a +}} diff --git a/src/arch/z80/peephole/opts/025_ld_h_a_oper_A.opt b/src/arch/z80/peephole/opts/025_ld_h_a_oper_A.opt index 60c7aa662..c76aef181 100644 --- a/src/arch/z80/peephole/opts/025_ld_h_a_oper_A.opt +++ b/src/arch/z80/peephole/opts/025_ld_h_a_oper_A.opt @@ -1,27 +1,27 @@ -;; Replace sequence: -;; ld h, r (r != a) -;; ld a, X -;; or/and/cp/add a,/sbc a,/sub h -;; -;; With: -;; ld a, X -;; or|xor|and|cp|add|sub r - - -OLEVEL: 1 -OFLAG: 25 - -REPLACE {{ - ld h, $1 - ld a, $2 - $3 h -}} - -IF {{ - ($1 <> a) && (($3 == or) || ($3 == xor) || ($3 == and) || ($3 == cp) || ($3 == sub) || ($3 == "add a,") || ($3 == "sbc a,")) -}} - -WITH {{ - ld a, $2 - $3 $1 -}} +;; Replace sequence: +;; ld h, r (r != a) +;; ld a, X +;; or/and/cp/add a,/sbc a,/sub h +;; +;; With: +;; ld a, X +;; or|xor|and|cp|add|sub r + + +OLEVEL: 1 +OFLAG: 25 + +REPLACE {{ + ld h, $1 + ld a, $2 + $3 h +}} + +IF {{ + ($1 <> a) && (($3 == or) || ($3 == xor) || ($3 == and) || ($3 == cp) || ($3 == sub) || ($3 == "add a,") || ($3 == "sbc a,")) +}} + +WITH {{ + ld a, $2 + $3 $1 +}} diff --git a/src/arch/z80/peephole/opts/027_ld_h_a_oper_A.opt b/src/arch/z80/peephole/opts/027_ld_h_a_oper_A.opt index d3af28ac2..3afb79b9f 100644 --- a/src/arch/z80/peephole/opts/027_ld_h_a_oper_A.opt +++ b/src/arch/z80/peephole/opts/027_ld_h_a_oper_A.opt @@ -1,22 +1,22 @@ -;; Replace sequence: -;; ld h, X -;; or h | and h -;; -;; With: -;; or X | and X - -OLEVEL: 1 -OFLAG: 27 - -REPLACE {{ - ld h, $1 - $2 h -}} - -IF {{ - ($2 IN (or, xor, and, cp, sub, "add a,", "sbc a,")) -}} - -WITH {{ - $2 $1 -}} +;; Replace sequence: +;; ld h, X +;; or h | and h +;; +;; With: +;; or X | and X + +OLEVEL: 1 +OFLAG: 27 + +REPLACE {{ + ld h, $1 + $2 h +}} + +IF {{ + ($2 IN (or, xor, and, cp, sub, "add a,", "sbc a,")) +}} + +WITH {{ + $2 $1 +}} diff --git a/src/arch/z80/peephole/opts/028_o2_pop_up.opt b/src/arch/z80/peephole/opts/028_o2_pop_up.opt index c57a27a46..855643104 100644 --- a/src/arch/z80/peephole/opts/028_o2_pop_up.opt +++ b/src/arch/z80/peephole/opts/028_o2_pop_up.opt @@ -1,28 +1,28 @@ -;; Replace sequence: -;; -;; pop rr -;; -;; With: -;; pop rr -;; -;; -;; This frees the stack ASAP and hopefully clash against a PUSH - - -OLEVEL: 2 -OFLAG: 28 - -REPLACE {{ - $2 - pop $1 -}} - -IF {{ - !(INSTR($2) IN (jp, jr, ret, call, djnz, rst)) && !NEEDS($2, (sp, $1)) && !IS_LABEL($2) && - OP1($2) <> "sp" && OP2($2) <> "sp" -}} - -WITH {{ - pop $1 - $2 -}} +;; Replace sequence: +;; +;; pop rr +;; +;; With: +;; pop rr +;; +;; +;; This frees the stack ASAP and hopefully clash against a PUSH + + +OLEVEL: 2 +OFLAG: 28 + +REPLACE {{ + $2 + pop $1 +}} + +IF {{ + !(INSTR($2) IN (jp, jr, ret, call, djnz, rst)) && !NEEDS($2, (sp, $1)) && !IS_LABEL($2) && + OP1($2) <> "sp" && OP2($2) <> "sp" +}} + +WITH {{ + pop $1 + $2 +}} diff --git a/src/arch/z80/peephole/opts/028_o3_pop_up.opt b/src/arch/z80/peephole/opts/028_o3_pop_up.opt index f2998a4ac..00f9ff0e4 100644 --- a/src/arch/z80/peephole/opts/028_o3_pop_up.opt +++ b/src/arch/z80/peephole/opts/028_o3_pop_up.opt @@ -1,27 +1,27 @@ -;; Replace sequence: -;; -;; pop rr -;; -;; With: -;; pop rr -;; -;; -;; This frees the stack ASAP and hopefully clash against a PUSH - -OLEVEL: 3 -OFLAG: 28 - -REPLACE {{ - $2 - pop $1 -}} - -IF {{ - !(INSTR($2) IN (jp, jr, ret, call, djnz, rst)) && !NEEDS($2, (sp, $1)) && !IS_LABEL($2) - && OP1($2) <> "sp" -}} - -WITH {{ - pop $1 - $2 -}} +;; Replace sequence: +;; +;; pop rr +;; +;; With: +;; pop rr +;; +;; +;; This frees the stack ASAP and hopefully clash against a PUSH + +OLEVEL: 3 +OFLAG: 28 + +REPLACE {{ + $2 + pop $1 +}} + +IF {{ + !(INSTR($2) IN (jp, jr, ret, call, djnz, rst)) && !NEEDS($2, (sp, $1)) && !IS_LABEL($2) + && OP1($2) <> "sp" +}} + +WITH {{ + pop $1 + $2 +}} diff --git a/src/arch/z80/peephole/opts/029_cp_0_or_a.opt b/src/arch/z80/peephole/opts/029_cp_0_or_a.opt index be249f8db..2a0642d68 100644 --- a/src/arch/z80/peephole/opts/029_cp_0_or_a.opt +++ b/src/arch/z80/peephole/opts/029_cp_0_or_a.opt @@ -1,20 +1,20 @@ -;; Replace sequence: -;; sub 0|cp 0 -;; -;; With: -;; or a - -OLEVEL: 1 -OFLAG: 29 - -REPLACE {{ - $1 0 -}} - -IF {{ - ($1 == cp) || ($1 == sub) -}} - -WITH {{ - or a -}} +;; Replace sequence: +;; sub 0|cp 0 +;; +;; With: +;; or a + +OLEVEL: 1 +OFLAG: 29 + +REPLACE {{ + $1 0 +}} + +IF {{ + ($1 == cp) || ($1 == sub) +}} + +WITH {{ + or a +}} diff --git a/src/arch/z80/peephole/opts/030_ora_jp_nc_jp.opt b/src/arch/z80/peephole/opts/030_ora_jp_nc_jp.opt index 9d439e145..34674817a 100644 --- a/src/arch/z80/peephole/opts/030_ora_jp_nc_jp.opt +++ b/src/arch/z80/peephole/opts/030_ora_jp_nc_jp.opt @@ -1,22 +1,22 @@ -;; Replace sequence: -;; or a | and a -;; jp nc, X | jr nc, X -;; -;; With: -;; jp X | jr X - -OLEVEL: 1 -OFLAG: 30 - -REPLACE {{ - $1 a - $2 nc, $3 -}} - -IF {{ - ($1 IN (or, xor, and, cp, sub)) && (($2 == jp) || ($2 == jr)) -}} - -WITH {{ - $2 $3 -}} +;; Replace sequence: +;; or a | and a +;; jp nc, X | jr nc, X +;; +;; With: +;; jp X | jr X + +OLEVEL: 1 +OFLAG: 30 + +REPLACE {{ + $1 a + $2 nc, $3 +}} + +IF {{ + ($1 IN (or, xor, and, cp, sub)) && (($2 == jp) || ($2 == jr)) +}} + +WITH {{ + $2 $3 +}} diff --git a/src/arch/z80/peephole/opts/031_jpX_Y_jpX.opt b/src/arch/z80/peephole/opts/031_jpX_Y_jpX.opt index 3a404e2e1..e142b6d73 100644 --- a/src/arch/z80/peephole/opts/031_jpX_Y_jpX.opt +++ b/src/arch/z80/peephole/opts/031_jpX_Y_jpX.opt @@ -1,25 +1,25 @@ -;; Removes useless (jumped over) instructions -;; Replace sequence: -;; jp X | jr X -;; YYYY -;; -;; With: -;; jp X | jr X -;; -;; YYYY must not be a label - -OLEVEL: 1 -OFLAG: 31 - -REPLACE {{ - $1 $2 - $3 -}} - -IF {{ - (($1 == jp) || ($1 == jr)) && (!IS_LABEL($3)) && (LEN($2) == 1) && (!IS_ASM($3)) -}} - -WITH {{ - $1 $2 -}} +;; Removes useless (jumped over) instructions +;; Replace sequence: +;; jp X | jr X +;; YYYY +;; +;; With: +;; jp X | jr X +;; +;; YYYY must not be a label + +OLEVEL: 1 +OFLAG: 31 + +REPLACE {{ + $1 $2 + $3 +}} + +IF {{ + (($1 == jp) || ($1 == jr)) && (!IS_LABEL($3)) && (LEN($2) == 1) && (!IS_ASM($3)) +}} + +WITH {{ + $1 $2 +}} diff --git a/src/arch/z80/peephole/opts/032_call_LOADSTR_ld_a1.opt b/src/arch/z80/peephole/opts/032_call_LOADSTR_ld_a1.opt index 9b9902746..7156931c5 100644 --- a/src/arch/z80/peephole/opts/032_call_LOADSTR_ld_a1.opt +++ b/src/arch/z80/peephole/opts/032_call_LOADSTR_ld_a1.opt @@ -1,22 +1,22 @@ -;; Replace sequence: -;; call __LOADSTR -;; ld a, 1 -;; call __PRINTSTR -;; -;; With: -;; xor a -;; call __PRINTSTR - -OLEVEL: 1 -OFLAG: 32 - -REPLACE {{ - call __LOADSTR - ld a, 1 - call __PRINTSTR -}} - -WITH {{ - xor a - call __PRINTSTR -}} +;; Replace sequence: +;; call __LOADSTR +;; ld a, 1 +;; call __PRINTSTR +;; +;; With: +;; xor a +;; call __PRINTSTR + +OLEVEL: 1 +OFLAG: 32 + +REPLACE {{ + call __LOADSTR + ld a, 1 + call __PRINTSTR +}} + +WITH {{ + xor a + call __PRINTSTR +}} diff --git a/src/arch/z80/peephole/opts/050_o1_ld_a_ld_h_pop.opt b/src/arch/z80/peephole/opts/050_o1_ld_a_ld_h_pop.opt index 8d1fc4825..e8623acdd 100644 --- a/src/arch/z80/peephole/opts/050_o1_ld_a_ld_h_pop.opt +++ b/src/arch/z80/peephole/opts/050_o1_ld_a_ld_h_pop.opt @@ -1,29 +1,29 @@ -;; Replace sequence: -;; ld a, X -;; ld h, a -;; ld a, Y -;; -;; With: -;; ld h, X -;; ld a, Y -;; -;; Whenever X is (hl), (ix + n) or a register - - -OLEVEL: 1 -OFLAG: 50 - -REPLACE {{ - ld a, $4 - ld h, a - ld a, $3 -}} - -IF {{ - (($4 == "(hl)") || (IS_INDIR($4)) || (IS_REG8($4))) -}} - -WITH {{ - ld h, $4 - ld a, $3 -}} +;; Replace sequence: +;; ld a, X +;; ld h, a +;; ld a, Y +;; +;; With: +;; ld h, X +;; ld a, Y +;; +;; Whenever X is (hl), (ix + n) or a register + + +OLEVEL: 1 +OFLAG: 50 + +REPLACE {{ + ld a, $4 + ld h, a + ld a, $3 +}} + +IF {{ + (($4 == "(hl)") || (IS_INDIR($4)) || (IS_REG8($4))) +}} + +WITH {{ + ld h, $4 + ld a, $3 +}} diff --git a/src/arch/z80/peephole/opts/051_o1_ld_a_ld_h_a_pop.opt b/src/arch/z80/peephole/opts/051_o1_ld_a_ld_h_a_pop.opt index d6cb6a736..505bd6279 100644 --- a/src/arch/z80/peephole/opts/051_o1_ld_a_ld_h_a_pop.opt +++ b/src/arch/z80/peephole/opts/051_o1_ld_a_ld_h_a_pop.opt @@ -1,29 +1,29 @@ -;; Replace sequence: -;; ld a, X -;; ld h, a -;; pop af -;; -;; With: -;; ld h, X -;; pop af -;; -;; Whenever X is (hl), (ix + n) a number or a register - - -OLEVEL: 1 -OFLAG: 50 - -REPLACE {{ - ld a, $1 - ld h, a - pop af -}} - -IF {{ - (($1 == "(hl)") || (IS_INDIR($1)) || (IS_REG8($1))) -}} - -WITH {{ - pop af - ld h, $1 -}} +;; Replace sequence: +;; ld a, X +;; ld h, a +;; pop af +;; +;; With: +;; ld h, X +;; pop af +;; +;; Whenever X is (hl), (ix + n) a number or a register + + +OLEVEL: 1 +OFLAG: 50 + +REPLACE {{ + ld a, $1 + ld h, a + pop af +}} + +IF {{ + (($1 == "(hl)") || (IS_INDIR($1)) || (IS_REG8($1))) +}} + +WITH {{ + pop af + ld h, $1 +}} diff --git a/src/arch/z80/peephole/opts/052_o3_ldhl_ld_a_l.opt b/src/arch/z80/peephole/opts/052_o3_ldhl_ld_a_l.opt index 4606ba779..98029b41a 100644 --- a/src/arch/z80/peephole/opts/052_o3_ldhl_ld_a_l.opt +++ b/src/arch/z80/peephole/opts/052_o3_ldhl_ld_a_l.opt @@ -1,22 +1,22 @@ -;; Replaces sequence: -;; ld hl, (_XXXX) -;; ld a, l -;; -;; With: -;; ld a, (_XXXX) - -OLEVEL: 3 -OFLAG: 52 - -REPLACE {{ - ld hl, ($1) - ld a, l -}} - -IF {{ - !IS_REQUIRED(hl) -}} - -WITH {{ - ld a, ($1) -}} +;; Replaces sequence: +;; ld hl, (_XXXX) +;; ld a, l +;; +;; With: +;; ld a, (_XXXX) + +OLEVEL: 3 +OFLAG: 52 + +REPLACE {{ + ld hl, ($1) + ld a, l +}} + +IF {{ + !IS_REQUIRED(hl) +}} + +WITH {{ + ld a, ($1) +}} diff --git a/src/arch/z80/peephole/opts/053_o1_ldh_nnn.opt b/src/arch/z80/peephole/opts/053_o1_ldh_nnn.opt index 59b0d39f4..58d686a32 100644 --- a/src/arch/z80/peephole/opts/053_o1_ldh_nnn.opt +++ b/src/arch/z80/peephole/opts/053_o1_ldh_nnn.opt @@ -1,21 +1,21 @@ -;; Replaces sequence: -;; ld a, (_XXXX) -;; ld h, a -;; -;; With: -;; ld hl, (_XXXX - 1) - -OLEVEL: 1 -OFLAG: 53 - -REPLACE {{ - ld a, ($1) - ld h, a - pop af -}} - - -WITH {{ - pop af - ld hl, ($1 - 1) -}} +;; Replaces sequence: +;; ld a, (_XXXX) +;; ld h, a +;; +;; With: +;; ld hl, (_XXXX - 1) + +OLEVEL: 1 +OFLAG: 53 + +REPLACE {{ + ld a, ($1) + ld h, a + pop af +}} + + +WITH {{ + pop af + ld hl, ($1 - 1) +}} diff --git a/src/arch/z80/peephole/opts/054_o3_ld_a_N_ld_hl_a.opt b/src/arch/z80/peephole/opts/054_o3_ld_a_N_ld_hl_a.opt index dcff2a0d8..af02599cb 100644 --- a/src/arch/z80/peephole/opts/054_o3_ld_a_N_ld_hl_a.opt +++ b/src/arch/z80/peephole/opts/054_o3_ld_a_N_ld_hl_a.opt @@ -1,24 +1,24 @@ -;; Replaces sequence: -;; ld hl, (_XXXX) -;; ld a, l -;; -;; With: -;; ld a, (_XXXX) - -OLEVEL: 3 -OFLAG: 54 - -REPLACE {{ - ld a, $1 - ld hl, ($2) - ld (hl), a -}} - -IF {{ - !IS_REQUIRED(a) && IS_IMMED($1) -}} - -WITH {{ - ld hl, ($2) - ld (hl), $1 -}} +;; Replaces sequence: +;; ld hl, (_XXXX) +;; ld a, l +;; +;; With: +;; ld a, (_XXXX) + +OLEVEL: 3 +OFLAG: 54 + +REPLACE {{ + ld a, $1 + ld hl, ($2) + ld (hl), a +}} + +IF {{ + !IS_REQUIRED(a) && IS_IMMED($1) +}} + +WITH {{ + ld hl, ($2) + ld (hl), $1 +}} diff --git a/src/arch/z80/peephole/opts/100_o3_ld_rr_N_ld_ss_N.opt b/src/arch/z80/peephole/opts/100_o3_ld_rr_N_ld_ss_N.opt index 201dfb972..99cc5cdd2 100644 --- a/src/arch/z80/peephole/opts/100_o3_ld_rr_N_ld_ss_N.opt +++ b/src/arch/z80/peephole/opts/100_o3_ld_rr_N_ld_ss_N.opt @@ -1,20 +1,20 @@ -;; Removes useless LD's -;; Tries to guess the value of the 1st and 2nd operands. -;; If they're the same, this LD is useless - -OLEVEL: 3 -OFLAG: 100 - -REPLACE {{ - ld $1, $2 -}} - -IF {{ - (GVAL($1) == GVAL($2)) || - ((IS_REG8($1) || IS_REG8($2)) && - (LOVAL(GVAL($1)) == LOVAL(GVAL($2))) - ) -}} - -WITH {{ -}} +;; Removes useless LD's +;; Tries to guess the value of the 1st and 2nd operands. +;; If they're the same, this LD is useless + +OLEVEL: 3 +OFLAG: 100 + +REPLACE {{ + ld $1, $2 +}} + +IF {{ + (GVAL($1) == GVAL($2)) || + ((IS_REG8($1) || IS_REG8($2)) && + (LOVAL(GVAL($1)) == LOVAL(GVAL($2))) + ) +}} + +WITH {{ +}} diff --git a/src/arch/z80/peephole/opts/101_o3_ld_rr_N_ld_h_r_ld_l_r.opt b/src/arch/z80/peephole/opts/101_o3_ld_rr_N_ld_h_r_ld_l_r.opt index 30b229046..028800e8d 100644 --- a/src/arch/z80/peephole/opts/101_o3_ld_rr_N_ld_h_r_ld_l_r.opt +++ b/src/arch/z80/peephole/opts/101_o3_ld_rr_N_ld_h_r_ld_l_r.opt @@ -1,36 +1,36 @@ -;; Replace sequence: -;; ld hl, N -;; ld b, h -;; ld c, l -;; -;; With: -;; ld bc, N -;; -;; If hl is not required later -;; also works with any other 16bit register - -OLEVEL: 3 -OFLAG: 101 - -REPLACE {{ - ld $1, $2 - ld $3, $4 - ld $5, $6 -}} - -IF {{ - ( - IS_REG16($1) && IS_REG16($7) && !IS_REQUIRED($1) - && IS_REG8($5) && IS_REG8($3) && IS_REG8($4) && IS_REG8($6) - && ((HIREG($1) == $4 && HIREG($7) == $3 && LOREG($1) == $6 && LOREG($7) == $5) || - (HIREG($1) == $6 && HIREG($7) == $5 && LOREG($1) == $4 && LOREG($7) == $3)) - ) -}} - -DEFINE {{ - $7 = IS_REG16($3 + $5) && ($3 + $5) || ($5 + $3) -}} - -WITH {{ - ld $7, $2 -}} +;; Replace sequence: +;; ld hl, N +;; ld b, h +;; ld c, l +;; +;; With: +;; ld bc, N +;; +;; If hl is not required later +;; also works with any other 16bit register + +OLEVEL: 3 +OFLAG: 101 + +REPLACE {{ + ld $1, $2 + ld $3, $4 + ld $5, $6 +}} + +IF {{ + ( + IS_REG16($1) && IS_REG16($7) && !IS_REQUIRED($1) + && IS_REG8($5) && IS_REG8($3) && IS_REG8($4) && IS_REG8($6) + && ((HIREG($1) == $4 && HIREG($7) == $3 && LOREG($1) == $6 && LOREG($7) == $5) || + (HIREG($1) == $6 && HIREG($7) == $5 && LOREG($1) == $4 && LOREG($7) == $3)) + ) +}} + +DEFINE {{ + $7 = IS_REG16($3 + $5) && ($3 + $5) || ($5 + $3) +}} + +WITH {{ + ld $7, $2 +}} diff --git a/src/arch/z80/peephole/opts/102_o3_ld_r_n_ld_r_n2.opt b/src/arch/z80/peephole/opts/102_o3_ld_r_n_ld_r_n2.opt index b8a491e11..b9947b2fc 100644 --- a/src/arch/z80/peephole/opts/102_o3_ld_r_n_ld_r_n2.opt +++ b/src/arch/z80/peephole/opts/102_o3_ld_r_n_ld_r_n2.opt @@ -1,18 +1,18 @@ -;; Removes useless LD's -;; Replaces; -;; LD r, XXX -;; LD r, YYY -;; With: -;; LD r, YYY - -OLEVEL: 3 -OFLAG: 102 - -REPLACE {{ - ld $1, $2 - ld $1, $3 -}} - -WITH {{ - ld $1, $3 -}} +;; Removes useless LD's +;; Replaces; +;; LD r, XXX +;; LD r, YYY +;; With: +;; LD r, YYY + +OLEVEL: 3 +OFLAG: 102 + +REPLACE {{ + ld $1, $2 + ld $1, $3 +}} + +WITH {{ + ld $1, $3 +}} diff --git a/src/arch/z80/peephole/opts/103_o3_or_and_a.opt b/src/arch/z80/peephole/opts/103_o3_or_and_a.opt index fb00a768e..51590a888 100644 --- a/src/arch/z80/peephole/opts/103_o3_or_and_a.opt +++ b/src/arch/z80/peephole/opts/103_o3_or_and_a.opt @@ -1,17 +1,17 @@ -;; Removes useless AND, OR a - -OLEVEL: 3 -OFLAG: 103 - -REPLACE {{ - $1 $2 -}} - -IF {{ - ($1 IN (and, or)) && - (($2 == a) || (!IS_REQUIRED(a))) && - !IS_REQUIRED(f) -}} - -WITH {{ -}} +;; Removes useless AND, OR a + +OLEVEL: 3 +OFLAG: 103 + +REPLACE {{ + $1 $2 +}} + +IF {{ + ($1 IN (and, or)) && + (($2 == a) || (!IS_REQUIRED(a))) && + !IS_REQUIRED(f) +}} + +WITH {{ +}} diff --git a/src/arch/z80/peephole/opts/103_o3_xor_a.opt b/src/arch/z80/peephole/opts/103_o3_xor_a.opt index 4467f3323..8db94ac6b 100644 --- a/src/arch/z80/peephole/opts/103_o3_xor_a.opt +++ b/src/arch/z80/peephole/opts/103_o3_xor_a.opt @@ -1,15 +1,15 @@ -;; Removes useless XOR a - -OLEVEL: 3 -OFLAG: 103 - -REPLACE {{ - xor a -}} - -IF {{ - (GVAL(a) == 0) && ((FLAGVAL(c) == 0 && FLAGVAL(z) == 1) || !IS_REQUIRED(f)) -}} - -WITH {{ -}} +;; Removes useless XOR a + +OLEVEL: 3 +OFLAG: 103 + +REPLACE {{ + xor a +}} + +IF {{ + (GVAL(a) == 0) && ((FLAGVAL(c) == 0 && FLAGVAL(z) == 1) || !IS_REQUIRED(f)) +}} + +WITH {{ +}} diff --git a/src/arch/z80/peephole/opts/104_o3_opt27.opt b/src/arch/z80/peephole/opts/104_o3_opt27.opt index 09409c591..3b1ff6a51 100644 --- a/src/arch/z80/peephole/opts/104_o3_opt27.opt +++ b/src/arch/z80/peephole/opts/104_o3_opt27.opt @@ -1,29 +1,29 @@ -;; Replaces -;; -;; ld r, XX -;; cp r | sub r | and r | or r | xor r -;; -;; With -;; cp XX | sub XX | and XX | or XX | xor XX -;; -;; IF r != a, and r is not required later -;; -;; Formerly OPT27 - -OLEVEL: 3 -OFLAG: 104 - -REPLACE {{ - ld $1, $2 - $3 $1 -}} - -IF {{ - ($1 <> a) && - !IS_REQUIRED($1) && !IS_REQUIRED(f) && - ($3 IN (cp, or, and, xor, sub)) -}} - -WITH {{ - $3 $2 -}} +;; Replaces +;; +;; ld r, XX +;; cp r | sub r | and r | or r | xor r +;; +;; With +;; cp XX | sub XX | and XX | or XX | xor XX +;; +;; IF r != a, and r is not required later +;; +;; Formerly OPT27 + +OLEVEL: 3 +OFLAG: 104 + +REPLACE {{ + ld $1, $2 + $3 $1 +}} + +IF {{ + ($1 <> a) && + !IS_REQUIRED($1) && !IS_REQUIRED(f) && + ($3 IN (cp, or, and, xor, sub)) +}} + +WITH {{ + $3 $2 +}} diff --git a/src/arch/z80/peephole/opts/105_o3_opt27.opt b/src/arch/z80/peephole/opts/105_o3_opt27.opt index f43b81c87..67abb36c1 100644 --- a/src/arch/z80/peephole/opts/105_o3_opt27.opt +++ b/src/arch/z80/peephole/opts/105_o3_opt27.opt @@ -1,29 +1,29 @@ -;; Replaces -;; -;; ld r, XX -;; add a, r | adc a, r | sbc a, r -;; -;; With -;; add a, XX | adc a, XX | sbc a, XX -;; -;; IF r != a, and r is not required later -;; -;; Formerly OPT27 - -OLEVEL: 3 -OFLAG: 105 - -REPLACE {{ - ld $1, $2 - $3 a, $1 -}} - -IF {{ - ($1 <> a) && - !IS_REQUIRED($1) && !IS_REQUIRED(f) && - ($3 IN (add, adc, sbc)) -}} - -WITH {{ - $3 a, $2 -}} +;; Replaces +;; +;; ld r, XX +;; add a, r | adc a, r | sbc a, r +;; +;; With +;; add a, XX | adc a, XX | sbc a, XX +;; +;; IF r != a, and r is not required later +;; +;; Formerly OPT27 + +OLEVEL: 3 +OFLAG: 105 + +REPLACE {{ + ld $1, $2 + $3 a, $1 +}} + +IF {{ + ($1 <> a) && + !IS_REQUIRED($1) && !IS_REQUIRED(f) && + ($3 IN (add, adc, sbc)) +}} + +WITH {{ + $3 a, $2 +}} diff --git a/src/arch/z80/peephole/opts/106_o3_exdehl.opt b/src/arch/z80/peephole/opts/106_o3_exdehl.opt index c250b69aa..db95cfcd3 100644 --- a/src/arch/z80/peephole/opts/106_o3_exdehl.opt +++ b/src/arch/z80/peephole/opts/106_o3_exdehl.opt @@ -1,23 +1,23 @@ -;; Replaces -;; -;; ld de, nn -;; -;; With -;; ex de, hl -;; -;; if hl is no longer required, and guessed val of hl is nn - -OLEVEL: 3 -OFLAG: 106 - -REPLACE {{ - ld de, $1 -}} - -IF {{ - GVAL(hl) == GVAL($1) && !IS_REQUIRED(hl) -}} - -WITH {{ - ex de, hl -}} +;; Replaces +;; +;; ld de, nn +;; +;; With +;; ex de, hl +;; +;; if hl is no longer required, and guessed val of hl is nn + +OLEVEL: 3 +OFLAG: 106 + +REPLACE {{ + ld de, $1 +}} + +IF {{ + GVAL(hl) == GVAL($1) && !IS_REQUIRED(hl) +}} + +WITH {{ + ex de, hl +}} diff --git a/src/arch/z80/peephole/opts/107_o3_exdehl_ldde.opt b/src/arch/z80/peephole/opts/107_o3_exdehl_ldde.opt index 78386b331..78147aa54 100644 --- a/src/arch/z80/peephole/opts/107_o3_exdehl_ldde.opt +++ b/src/arch/z80/peephole/opts/107_o3_exdehl_ldde.opt @@ -1,25 +1,25 @@ -;; Replaces -;; -;; ld hl, nn -;; ex de, hl -;; -;; With -;; ld de, nn -;; -;; if hl is no longer required - -OLEVEL: 3 -OFLAG: 107 - -REPLACE {{ - ld hl, $1 - ex de, hl -}} - -IF {{ - !IS_REQUIRED(hl) -}} - -WITH {{ - ld de, $1 -}} +;; Replaces +;; +;; ld hl, nn +;; ex de, hl +;; +;; With +;; ld de, nn +;; +;; if hl is no longer required + +OLEVEL: 3 +OFLAG: 107 + +REPLACE {{ + ld hl, $1 + ex de, hl +}} + +IF {{ + !IS_REQUIRED(hl) +}} + +WITH {{ + ld de, $1 +}} diff --git a/src/arch/z80/peephole/opts/108_o3_inc_dec.opt b/src/arch/z80/peephole/opts/108_o3_inc_dec.opt index e7849f0e0..2d7f5a24b 100644 --- a/src/arch/z80/peephole/opts/108_o3_inc_dec.opt +++ b/src/arch/z80/peephole/opts/108_o3_inc_dec.opt @@ -1,28 +1,28 @@ -;; Replaces -;; -;; ld rr, nn -;; -;; With -;; inc / dec rr -;; -;; If the previous known value of rr is nn - / + 1 - - -OLEVEL: 3 -OFLAG: 108 - -REPLACE {{ - ld $1, $2 -}} - -DEFINE {{ - $3 = ($2 == GVAL($1) .+ 1) && "inc" || ($2 == GVAL($1) .- 1) && "dec" -}} - -IF {{ - IS_REG16($1) && $3 <> "" -}} - -WITH {{ - $3 $1 -}} +;; Replaces +;; +;; ld rr, nn +;; +;; With +;; inc / dec rr +;; +;; If the previous known value of rr is nn - / + 1 + + +OLEVEL: 3 +OFLAG: 108 + +REPLACE {{ + ld $1, $2 +}} + +DEFINE {{ + $3 = ($2 == GVAL($1) .+ 1) && "inc" || ($2 == GVAL($1) .- 1) && "dec" +}} + +IF {{ + IS_REG16($1) && $3 <> "" +}} + +WITH {{ + $3 $1 +}} diff --git a/src/arch/z80/peephole/opts/109_o4_ld_mem_op_ld_mem.opt b/src/arch/z80/peephole/opts/109_o4_ld_mem_op_ld_mem.opt index 713718dd2..95e730ed7 100644 --- a/src/arch/z80/peephole/opts/109_o4_ld_mem_op_ld_mem.opt +++ b/src/arch/z80/peephole/opts/109_o4_ld_mem_op_ld_mem.opt @@ -1,17 +1,17 @@ -;; Removes useless operation -;; Tries to guess if the result of the operation LD, OR, AND, XOR, ADC, ADD, SBC is later used - -OLEVEL: 4 -OFLAG: 109 - -REPLACE {{ - $1 $2, $3 -}} - -IF {{ -($2 <> sp) && ($2 <> ix) && !IS_REQUIRED($2) && - ($1 == ld || (!IS_REQUIRED(f) && ($1 == add || $1 == adc || $1 == sbc))) -}} - -WITH {{ -}} +;; Removes useless operation +;; Tries to guess if the result of the operation LD, OR, AND, XOR, ADC, ADD, SBC is later used + +OLEVEL: 4 +OFLAG: 109 + +REPLACE {{ + $1 $2, $3 +}} + +IF {{ +($2 <> sp) && ($2 <> ix) && !IS_REQUIRED($2) && + ($1 == ld || (!IS_REQUIRED(f) && ($1 == add || $1 == adc || $1 == sbc))) +}} + +WITH {{ +}} diff --git a/src/arch/z80/peephole/opts/110_o4_ld_mem_op_ld_mem.opt b/src/arch/z80/peephole/opts/110_o4_ld_mem_op_ld_mem.opt index aa76419ef..3780f39d5 100644 --- a/src/arch/z80/peephole/opts/110_o4_ld_mem_op_ld_mem.opt +++ b/src/arch/z80/peephole/opts/110_o4_ld_mem_op_ld_mem.opt @@ -1,19 +1,19 @@ -;; Removes useless LD's -;; Tries to guess the value of the 1st and 2nd operands. -;; If they're the same, this LD is useless - -OLEVEL: 4 -OFLAG: 110 - -REPLACE {{ - $1 $2 -}} - -IF {{ - ((($1 == inc || $1 == dec) && !IS_REQUIRED($2)) - || ($1 == sub && !IS_REQUIRED(a)) - ) && ($2 <> sp) && !IS_REQUIRED(f) -}} - -WITH {{ -}} +;; Removes useless LD's +;; Tries to guess the value of the 1st and 2nd operands. +;; If they're the same, this LD is useless + +OLEVEL: 4 +OFLAG: 110 + +REPLACE {{ + $1 $2 +}} + +IF {{ + ((($1 == inc || $1 == dec) && !IS_REQUIRED($2)) + || ($1 == sub && !IS_REQUIRED(a)) + ) && ($2 <> sp) && !IS_REQUIRED(f) +}} + +WITH {{ +}} diff --git a/src/arch/zxnext/peephole/opts/060_o1_mul_de.opt b/src/arch/zxnext/peephole/opts/060_o1_mul_de.opt index 962b9bc3a..75f4b0133 100644 --- a/src/arch/zxnext/peephole/opts/060_o1_mul_de.opt +++ b/src/arch/zxnext/peephole/opts/060_o1_mul_de.opt @@ -1,25 +1,25 @@ -;; Change sequence: -;; ld h, XX -;; ld d, h - -;; into: - -;; ld d, XX - -;; if it's used for an 8 bit multiplication (mul d, e) - -OLEVEL: 1 -OFLAG: 1 - -REPLACE {{ - ld h, $1 - ld d, h - ld e, a - mul d, e -}} - -WITH {{ - ld d, $1 - ld e, a - mul d, e -}} +;; Change sequence: +;; ld h, XX +;; ld d, h + +;; into: + +;; ld d, XX + +;; if it's used for an 8 bit multiplication (mul d, e) + +OLEVEL: 1 +OFLAG: 1 + +REPLACE {{ + ld h, $1 + ld d, h + ld e, a + mul d, e +}} + +WITH {{ + ld d, $1 + ld e, a + mul d, e +}} diff --git a/src/lib/arch/zx48k/runtime/spectranet.inc b/src/lib/arch/zx48k/runtime/spectranet.inc index 052e61f22..73fbd2c1e 100644 --- a/src/lib/arch/zx48k/runtime/spectranet.inc +++ b/src/lib/arch/zx48k/runtime/spectranet.inc @@ -1,143 +1,143 @@ -;The MIT License -; -;Copyright (c) 2008 Dylan Smith -; -;Permission is hereby granted, free of charge, to any person obtaining a copy -;of this software and associated documentation files (the "Software"), to deal -;in the Software without restriction, including without limitation the rights -;to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -;copies of the Software, and to permit persons to whom the Software is -;furnished to do so, subject to the following conditions: -; -;The above copyright notice and this permission notice shall be included in -;all copies or substantial portions of the Software. -; -;THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -;IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -;FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -;AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -;LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -;OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -;THE SOFTWARE. - -; This file can be included in assembly language programs to give -; symbolic access to the public jump table entry points. - -; Avoid double inclusion -#ifndef __SPECTRANET_INC__ -#define __SPECTRANET_INC__ - -NAMESPACE Spectranet ; NAME PREFIX to avoid naming clash - -; Hardware page-in entry points -MODULECALL equ $3FF8 -MODULECALL_NOPAGE equ $28 -PAGEIN equ $3FF9 -PAGEOUT equ $007C -HLCALL equ $3FFA -IXCALL equ $3FFD - -; Port defines -CTRLREG equ $033B -CPLDINFO equ $023B - -; Jump table entry points -SOCKET equ $3E00 ; Allocate a socket -CLOSE equ $3E03 ; Close a socket -LISTEN equ $3E06 ; Listen for incoming connections -ACCEPT equ $3E09 ; Accept an incoming connection -BIND equ $3E0C ; Bind a local address to a socket -CONNECT equ $3E0F ; Connect to a remote host -SEND equ $3E12 ; Send data -RECV equ $3E15 ; Receive data -SENDTO equ $3E18 ; Send data to an address -RECVFROM equ $3E1B ; Receive data from an address -POLL equ $3E1E ; Poll a list of sockets -POLLALL equ $3E21 ; Poll all open sockets -POLLFD equ $3E24 ; Poll a single socket -GETHOSTBYNAME equ $3E27 ; Look up a hostname -PUTCHAR42 equ $3E2A ; 42 column print write a character -PRINT42 equ $3E2D ; 42 column print a null terminated string -CLEAR42 equ $3E30 ; Clear the screen and reset 42-col print -SETPAGEA equ $3E33 ; Sets page area A -SETPAGEB equ $3E36 ; Sets page area B -LONG2IPSTRING equ $3E39 ; Convert a 4 byte big endian long to an IP -IPSTRING2LONG equ $3E3C ; Convert an IP to a 4 byte big endian long -ITOA8 equ $3E3F ; Convert a byte to ascii -RAND16 equ $3E42 ; 16 bit PRNG -REMOTEADDRESS equ $3E45 ; Fill struct sockaddr_in -IFCONFIG_INET equ $3E48 ; Set IPv4 address -IFCONFIG_NETMASK equ $3E4B ; Set netmask -IFCONFIG_GW equ $3E4E ; Set gateway -INITHW equ $3E51 ; Set the MAC address and initial hw registers -GETHWADDR equ $3E54 ; Read the MAC address -DECONFIG equ $3E57 ; Deconfigure inet, netmask and gateway -MAC2STRING equ $3E5A ; Convert 6 byte MAC address to a string -STRING2MAC equ $3E5D ; Convert a hex string to a 6 byte MAC address -ITOH8 equ $3E60 ; Convert accumulator to hex string -HTOI8 equ $3E63 ; Convert hex string to byte in A -GETKEY equ $3E66 ; Get a key from the keyboard, and put it in A -KEYUP equ $3E69 ; Wait for key release -INPUTSTRING equ $3E6C ; Read a string into buffer at DE -GET_IFCONFIG_INET equ $3E6F ; Gets the current IPv4 address -GET_IFCONFIG_NETMASK equ $3E72 ; Gets the current netmask -GET_IFCONFIG_GW equ $3E75 ; Gets the current gateway address -SETTRAP equ $3E78 ; Sets the programmable trap -DISABLETRAP equ $3E7B ; Disables the programmable trap -ENABLETRAP equ $3E7E ; Enables the programmable trap -PUSHPAGEA equ $3E81 ; Pages a page into area A, pushing the old one -POPPAGEA equ $3E84 ; Restores the previous page in area A -PUSHPAGEB equ $3E87 ; Pages into area B pushing the old one -POPPAGEB equ $3E8A ; Restores the previous page in area B -PAGETRAPRETURN equ $3E8D ; Returns from a trap to page area B -TRAPRETURN equ $3E90 ; Returns from a trap that didn't page area B -ADDBASICEXT equ $3E93 ; Adds a BASIC command -STATEMENT_END equ $3E96 ; Check for statement end, exit at syntax time -EXIT_SUCCESS equ $3E99 ; Use this to exit successfully after cmd -PARSE_ERROR equ $3E9C ; Use this to exit to BASIC with a parse error -RESERVEPAGE equ $3E9F ; Reserve a page of static RAM -FREEPAGE equ $3EA2 ; Free a page of static RAM -REPORTERR equ $3EA5 ; report an error via BASIC - -; Filesystem functions -MOUNT equ $3EA8 -UMOUNT equ $3EAB -OPENDIR equ $3EAE -OPEN equ $3EB1 -UNLINK equ $3EB4 -MKDIR equ $3EB7 -RMDIR equ $3EBA -SIZE equ $3EBD -FREE equ $3EC0 -STAT equ $3EC3 -CHMOD equ $3EC6 -READ equ $3EC9 -WRITE equ $3ECC -LSEEK equ $3ECF -VCLOSE equ $3ED2 -VPOLL equ $3ED5 -READDIR equ $3ED8 -CLOSEDIR equ $3EDB -CHDIR equ $3EDE -GETCWD equ $3EE1 -RENAME equ $3EE4 -SETMOUNTPOINT equ $3EE7 -FREEMOUNTPOINT equ $3EEA -RESALLOC equ $3EED - - -; Definitions -ALLOCFD equ 1 -FREEFD equ 0 -ALLOCDIRHND equ 3 -FREEDIRHND equ 2 - -; POLL status bits -BIT_RECV equ 2 -BIT_DISCON equ 1 -BIT_CONN equ 0 - -NAMESPACE DEFAULT ; Clears namespace - -#endif - +;The MIT License +; +;Copyright (c) 2008 Dylan Smith +; +;Permission is hereby granted, free of charge, to any person obtaining a copy +;of this software and associated documentation files (the "Software"), to deal +;in the Software without restriction, including without limitation the rights +;to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +;copies of the Software, and to permit persons to whom the Software is +;furnished to do so, subject to the following conditions: +; +;The above copyright notice and this permission notice shall be included in +;all copies or substantial portions of the Software. +; +;THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +;THE SOFTWARE. + +; This file can be included in assembly language programs to give +; symbolic access to the public jump table entry points. + +; Avoid double inclusion +#ifndef __SPECTRANET_INC__ +#define __SPECTRANET_INC__ + +NAMESPACE Spectranet ; NAME PREFIX to avoid naming clash + +; Hardware page-in entry points +MODULECALL equ $3FF8 +MODULECALL_NOPAGE equ $28 +PAGEIN equ $3FF9 +PAGEOUT equ $007C +HLCALL equ $3FFA +IXCALL equ $3FFD + +; Port defines +CTRLREG equ $033B +CPLDINFO equ $023B + +; Jump table entry points +SOCKET equ $3E00 ; Allocate a socket +CLOSE equ $3E03 ; Close a socket +LISTEN equ $3E06 ; Listen for incoming connections +ACCEPT equ $3E09 ; Accept an incoming connection +BIND equ $3E0C ; Bind a local address to a socket +CONNECT equ $3E0F ; Connect to a remote host +SEND equ $3E12 ; Send data +RECV equ $3E15 ; Receive data +SENDTO equ $3E18 ; Send data to an address +RECVFROM equ $3E1B ; Receive data from an address +POLL equ $3E1E ; Poll a list of sockets +POLLALL equ $3E21 ; Poll all open sockets +POLLFD equ $3E24 ; Poll a single socket +GETHOSTBYNAME equ $3E27 ; Look up a hostname +PUTCHAR42 equ $3E2A ; 42 column print write a character +PRINT42 equ $3E2D ; 42 column print a null terminated string +CLEAR42 equ $3E30 ; Clear the screen and reset 42-col print +SETPAGEA equ $3E33 ; Sets page area A +SETPAGEB equ $3E36 ; Sets page area B +LONG2IPSTRING equ $3E39 ; Convert a 4 byte big endian long to an IP +IPSTRING2LONG equ $3E3C ; Convert an IP to a 4 byte big endian long +ITOA8 equ $3E3F ; Convert a byte to ascii +RAND16 equ $3E42 ; 16 bit PRNG +REMOTEADDRESS equ $3E45 ; Fill struct sockaddr_in +IFCONFIG_INET equ $3E48 ; Set IPv4 address +IFCONFIG_NETMASK equ $3E4B ; Set netmask +IFCONFIG_GW equ $3E4E ; Set gateway +INITHW equ $3E51 ; Set the MAC address and initial hw registers +GETHWADDR equ $3E54 ; Read the MAC address +DECONFIG equ $3E57 ; Deconfigure inet, netmask and gateway +MAC2STRING equ $3E5A ; Convert 6 byte MAC address to a string +STRING2MAC equ $3E5D ; Convert a hex string to a 6 byte MAC address +ITOH8 equ $3E60 ; Convert accumulator to hex string +HTOI8 equ $3E63 ; Convert hex string to byte in A +GETKEY equ $3E66 ; Get a key from the keyboard, and put it in A +KEYUP equ $3E69 ; Wait for key release +INPUTSTRING equ $3E6C ; Read a string into buffer at DE +GET_IFCONFIG_INET equ $3E6F ; Gets the current IPv4 address +GET_IFCONFIG_NETMASK equ $3E72 ; Gets the current netmask +GET_IFCONFIG_GW equ $3E75 ; Gets the current gateway address +SETTRAP equ $3E78 ; Sets the programmable trap +DISABLETRAP equ $3E7B ; Disables the programmable trap +ENABLETRAP equ $3E7E ; Enables the programmable trap +PUSHPAGEA equ $3E81 ; Pages a page into area A, pushing the old one +POPPAGEA equ $3E84 ; Restores the previous page in area A +PUSHPAGEB equ $3E87 ; Pages into area B pushing the old one +POPPAGEB equ $3E8A ; Restores the previous page in area B +PAGETRAPRETURN equ $3E8D ; Returns from a trap to page area B +TRAPRETURN equ $3E90 ; Returns from a trap that didn't page area B +ADDBASICEXT equ $3E93 ; Adds a BASIC command +STATEMENT_END equ $3E96 ; Check for statement end, exit at syntax time +EXIT_SUCCESS equ $3E99 ; Use this to exit successfully after cmd +PARSE_ERROR equ $3E9C ; Use this to exit to BASIC with a parse error +RESERVEPAGE equ $3E9F ; Reserve a page of static RAM +FREEPAGE equ $3EA2 ; Free a page of static RAM +REPORTERR equ $3EA5 ; report an error via BASIC + +; Filesystem functions +MOUNT equ $3EA8 +UMOUNT equ $3EAB +OPENDIR equ $3EAE +OPEN equ $3EB1 +UNLINK equ $3EB4 +MKDIR equ $3EB7 +RMDIR equ $3EBA +SIZE equ $3EBD +FREE equ $3EC0 +STAT equ $3EC3 +CHMOD equ $3EC6 +READ equ $3EC9 +WRITE equ $3ECC +LSEEK equ $3ECF +VCLOSE equ $3ED2 +VPOLL equ $3ED5 +READDIR equ $3ED8 +CLOSEDIR equ $3EDB +CHDIR equ $3EDE +GETCWD equ $3EE1 +RENAME equ $3EE4 +SETMOUNTPOINT equ $3EE7 +FREEMOUNTPOINT equ $3EEA +RESALLOC equ $3EED + + +; Definitions +ALLOCFD equ 1 +FREEFD equ 0 +ALLOCDIRHND equ 3 +FREEDIRHND equ 2 + +; POLL status bits +BIT_RECV equ 2 +BIT_DISCON equ 1 +BIT_CONN equ 0 + +NAMESPACE DEFAULT ; Clears namespace + +#endif + diff --git a/src/lib/arch/zx48k/stdlib/README b/src/lib/arch/zx48k/stdlib/README index 401199747..7d24b7e8a 100644 --- a/src/lib/arch/zx48k/stdlib/README +++ b/src/lib/arch/zx48k/stdlib/README @@ -1,4 +1,4 @@ -This directory contains a library of "basic" BASIC functions. -Some of them are related to ZX Spectrum BASIC (i.e. ATTR) while -others are more standard to FREE BASIC (csrlin or pos) - +This directory contains a library of "basic" BASIC functions. +Some of them are related to ZX Spectrum BASIC (i.e. ATTR) while +others are more standard to FREE BASIC (csrlin or pos) + diff --git a/src/lib/arch/zxnext/runtime/spectranet.inc b/src/lib/arch/zxnext/runtime/spectranet.inc index 052e61f22..73fbd2c1e 100644 --- a/src/lib/arch/zxnext/runtime/spectranet.inc +++ b/src/lib/arch/zxnext/runtime/spectranet.inc @@ -1,143 +1,143 @@ -;The MIT License -; -;Copyright (c) 2008 Dylan Smith -; -;Permission is hereby granted, free of charge, to any person obtaining a copy -;of this software and associated documentation files (the "Software"), to deal -;in the Software without restriction, including without limitation the rights -;to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -;copies of the Software, and to permit persons to whom the Software is -;furnished to do so, subject to the following conditions: -; -;The above copyright notice and this permission notice shall be included in -;all copies or substantial portions of the Software. -; -;THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -;IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -;FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -;AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -;LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -;OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -;THE SOFTWARE. - -; This file can be included in assembly language programs to give -; symbolic access to the public jump table entry points. - -; Avoid double inclusion -#ifndef __SPECTRANET_INC__ -#define __SPECTRANET_INC__ - -NAMESPACE Spectranet ; NAME PREFIX to avoid naming clash - -; Hardware page-in entry points -MODULECALL equ $3FF8 -MODULECALL_NOPAGE equ $28 -PAGEIN equ $3FF9 -PAGEOUT equ $007C -HLCALL equ $3FFA -IXCALL equ $3FFD - -; Port defines -CTRLREG equ $033B -CPLDINFO equ $023B - -; Jump table entry points -SOCKET equ $3E00 ; Allocate a socket -CLOSE equ $3E03 ; Close a socket -LISTEN equ $3E06 ; Listen for incoming connections -ACCEPT equ $3E09 ; Accept an incoming connection -BIND equ $3E0C ; Bind a local address to a socket -CONNECT equ $3E0F ; Connect to a remote host -SEND equ $3E12 ; Send data -RECV equ $3E15 ; Receive data -SENDTO equ $3E18 ; Send data to an address -RECVFROM equ $3E1B ; Receive data from an address -POLL equ $3E1E ; Poll a list of sockets -POLLALL equ $3E21 ; Poll all open sockets -POLLFD equ $3E24 ; Poll a single socket -GETHOSTBYNAME equ $3E27 ; Look up a hostname -PUTCHAR42 equ $3E2A ; 42 column print write a character -PRINT42 equ $3E2D ; 42 column print a null terminated string -CLEAR42 equ $3E30 ; Clear the screen and reset 42-col print -SETPAGEA equ $3E33 ; Sets page area A -SETPAGEB equ $3E36 ; Sets page area B -LONG2IPSTRING equ $3E39 ; Convert a 4 byte big endian long to an IP -IPSTRING2LONG equ $3E3C ; Convert an IP to a 4 byte big endian long -ITOA8 equ $3E3F ; Convert a byte to ascii -RAND16 equ $3E42 ; 16 bit PRNG -REMOTEADDRESS equ $3E45 ; Fill struct sockaddr_in -IFCONFIG_INET equ $3E48 ; Set IPv4 address -IFCONFIG_NETMASK equ $3E4B ; Set netmask -IFCONFIG_GW equ $3E4E ; Set gateway -INITHW equ $3E51 ; Set the MAC address and initial hw registers -GETHWADDR equ $3E54 ; Read the MAC address -DECONFIG equ $3E57 ; Deconfigure inet, netmask and gateway -MAC2STRING equ $3E5A ; Convert 6 byte MAC address to a string -STRING2MAC equ $3E5D ; Convert a hex string to a 6 byte MAC address -ITOH8 equ $3E60 ; Convert accumulator to hex string -HTOI8 equ $3E63 ; Convert hex string to byte in A -GETKEY equ $3E66 ; Get a key from the keyboard, and put it in A -KEYUP equ $3E69 ; Wait for key release -INPUTSTRING equ $3E6C ; Read a string into buffer at DE -GET_IFCONFIG_INET equ $3E6F ; Gets the current IPv4 address -GET_IFCONFIG_NETMASK equ $3E72 ; Gets the current netmask -GET_IFCONFIG_GW equ $3E75 ; Gets the current gateway address -SETTRAP equ $3E78 ; Sets the programmable trap -DISABLETRAP equ $3E7B ; Disables the programmable trap -ENABLETRAP equ $3E7E ; Enables the programmable trap -PUSHPAGEA equ $3E81 ; Pages a page into area A, pushing the old one -POPPAGEA equ $3E84 ; Restores the previous page in area A -PUSHPAGEB equ $3E87 ; Pages into area B pushing the old one -POPPAGEB equ $3E8A ; Restores the previous page in area B -PAGETRAPRETURN equ $3E8D ; Returns from a trap to page area B -TRAPRETURN equ $3E90 ; Returns from a trap that didn't page area B -ADDBASICEXT equ $3E93 ; Adds a BASIC command -STATEMENT_END equ $3E96 ; Check for statement end, exit at syntax time -EXIT_SUCCESS equ $3E99 ; Use this to exit successfully after cmd -PARSE_ERROR equ $3E9C ; Use this to exit to BASIC with a parse error -RESERVEPAGE equ $3E9F ; Reserve a page of static RAM -FREEPAGE equ $3EA2 ; Free a page of static RAM -REPORTERR equ $3EA5 ; report an error via BASIC - -; Filesystem functions -MOUNT equ $3EA8 -UMOUNT equ $3EAB -OPENDIR equ $3EAE -OPEN equ $3EB1 -UNLINK equ $3EB4 -MKDIR equ $3EB7 -RMDIR equ $3EBA -SIZE equ $3EBD -FREE equ $3EC0 -STAT equ $3EC3 -CHMOD equ $3EC6 -READ equ $3EC9 -WRITE equ $3ECC -LSEEK equ $3ECF -VCLOSE equ $3ED2 -VPOLL equ $3ED5 -READDIR equ $3ED8 -CLOSEDIR equ $3EDB -CHDIR equ $3EDE -GETCWD equ $3EE1 -RENAME equ $3EE4 -SETMOUNTPOINT equ $3EE7 -FREEMOUNTPOINT equ $3EEA -RESALLOC equ $3EED - - -; Definitions -ALLOCFD equ 1 -FREEFD equ 0 -ALLOCDIRHND equ 3 -FREEDIRHND equ 2 - -; POLL status bits -BIT_RECV equ 2 -BIT_DISCON equ 1 -BIT_CONN equ 0 - -NAMESPACE DEFAULT ; Clears namespace - -#endif - +;The MIT License +; +;Copyright (c) 2008 Dylan Smith +; +;Permission is hereby granted, free of charge, to any person obtaining a copy +;of this software and associated documentation files (the "Software"), to deal +;in the Software without restriction, including without limitation the rights +;to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +;copies of the Software, and to permit persons to whom the Software is +;furnished to do so, subject to the following conditions: +; +;The above copyright notice and this permission notice shall be included in +;all copies or substantial portions of the Software. +; +;THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +;IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +;FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +;AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +;LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +;OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +;THE SOFTWARE. + +; This file can be included in assembly language programs to give +; symbolic access to the public jump table entry points. + +; Avoid double inclusion +#ifndef __SPECTRANET_INC__ +#define __SPECTRANET_INC__ + +NAMESPACE Spectranet ; NAME PREFIX to avoid naming clash + +; Hardware page-in entry points +MODULECALL equ $3FF8 +MODULECALL_NOPAGE equ $28 +PAGEIN equ $3FF9 +PAGEOUT equ $007C +HLCALL equ $3FFA +IXCALL equ $3FFD + +; Port defines +CTRLREG equ $033B +CPLDINFO equ $023B + +; Jump table entry points +SOCKET equ $3E00 ; Allocate a socket +CLOSE equ $3E03 ; Close a socket +LISTEN equ $3E06 ; Listen for incoming connections +ACCEPT equ $3E09 ; Accept an incoming connection +BIND equ $3E0C ; Bind a local address to a socket +CONNECT equ $3E0F ; Connect to a remote host +SEND equ $3E12 ; Send data +RECV equ $3E15 ; Receive data +SENDTO equ $3E18 ; Send data to an address +RECVFROM equ $3E1B ; Receive data from an address +POLL equ $3E1E ; Poll a list of sockets +POLLALL equ $3E21 ; Poll all open sockets +POLLFD equ $3E24 ; Poll a single socket +GETHOSTBYNAME equ $3E27 ; Look up a hostname +PUTCHAR42 equ $3E2A ; 42 column print write a character +PRINT42 equ $3E2D ; 42 column print a null terminated string +CLEAR42 equ $3E30 ; Clear the screen and reset 42-col print +SETPAGEA equ $3E33 ; Sets page area A +SETPAGEB equ $3E36 ; Sets page area B +LONG2IPSTRING equ $3E39 ; Convert a 4 byte big endian long to an IP +IPSTRING2LONG equ $3E3C ; Convert an IP to a 4 byte big endian long +ITOA8 equ $3E3F ; Convert a byte to ascii +RAND16 equ $3E42 ; 16 bit PRNG +REMOTEADDRESS equ $3E45 ; Fill struct sockaddr_in +IFCONFIG_INET equ $3E48 ; Set IPv4 address +IFCONFIG_NETMASK equ $3E4B ; Set netmask +IFCONFIG_GW equ $3E4E ; Set gateway +INITHW equ $3E51 ; Set the MAC address and initial hw registers +GETHWADDR equ $3E54 ; Read the MAC address +DECONFIG equ $3E57 ; Deconfigure inet, netmask and gateway +MAC2STRING equ $3E5A ; Convert 6 byte MAC address to a string +STRING2MAC equ $3E5D ; Convert a hex string to a 6 byte MAC address +ITOH8 equ $3E60 ; Convert accumulator to hex string +HTOI8 equ $3E63 ; Convert hex string to byte in A +GETKEY equ $3E66 ; Get a key from the keyboard, and put it in A +KEYUP equ $3E69 ; Wait for key release +INPUTSTRING equ $3E6C ; Read a string into buffer at DE +GET_IFCONFIG_INET equ $3E6F ; Gets the current IPv4 address +GET_IFCONFIG_NETMASK equ $3E72 ; Gets the current netmask +GET_IFCONFIG_GW equ $3E75 ; Gets the current gateway address +SETTRAP equ $3E78 ; Sets the programmable trap +DISABLETRAP equ $3E7B ; Disables the programmable trap +ENABLETRAP equ $3E7E ; Enables the programmable trap +PUSHPAGEA equ $3E81 ; Pages a page into area A, pushing the old one +POPPAGEA equ $3E84 ; Restores the previous page in area A +PUSHPAGEB equ $3E87 ; Pages into area B pushing the old one +POPPAGEB equ $3E8A ; Restores the previous page in area B +PAGETRAPRETURN equ $3E8D ; Returns from a trap to page area B +TRAPRETURN equ $3E90 ; Returns from a trap that didn't page area B +ADDBASICEXT equ $3E93 ; Adds a BASIC command +STATEMENT_END equ $3E96 ; Check for statement end, exit at syntax time +EXIT_SUCCESS equ $3E99 ; Use this to exit successfully after cmd +PARSE_ERROR equ $3E9C ; Use this to exit to BASIC with a parse error +RESERVEPAGE equ $3E9F ; Reserve a page of static RAM +FREEPAGE equ $3EA2 ; Free a page of static RAM +REPORTERR equ $3EA5 ; report an error via BASIC + +; Filesystem functions +MOUNT equ $3EA8 +UMOUNT equ $3EAB +OPENDIR equ $3EAE +OPEN equ $3EB1 +UNLINK equ $3EB4 +MKDIR equ $3EB7 +RMDIR equ $3EBA +SIZE equ $3EBD +FREE equ $3EC0 +STAT equ $3EC3 +CHMOD equ $3EC6 +READ equ $3EC9 +WRITE equ $3ECC +LSEEK equ $3ECF +VCLOSE equ $3ED2 +VPOLL equ $3ED5 +READDIR equ $3ED8 +CLOSEDIR equ $3EDB +CHDIR equ $3EDE +GETCWD equ $3EE1 +RENAME equ $3EE4 +SETMOUNTPOINT equ $3EE7 +FREEMOUNTPOINT equ $3EEA +RESALLOC equ $3EED + + +; Definitions +ALLOCFD equ 1 +FREEFD equ 0 +ALLOCDIRHND equ 3 +FREEDIRHND equ 2 + +; POLL status bits +BIT_RECV equ 2 +BIT_DISCON equ 1 +BIT_CONN equ 0 + +NAMESPACE DEFAULT ; Clears namespace + +#endif + diff --git a/src/lib/arch/zxnext/stdlib/README b/src/lib/arch/zxnext/stdlib/README index 401199747..7d24b7e8a 100644 --- a/src/lib/arch/zxnext/stdlib/README +++ b/src/lib/arch/zxnext/stdlib/README @@ -1,4 +1,4 @@ -This directory contains a library of "basic" BASIC functions. -Some of them are related to ZX Spectrum BASIC (i.e. ATTR) while -others are more standard to FREE BASIC (csrlin or pos) - +This directory contains a library of "basic" BASIC functions. +Some of them are related to ZX Spectrum BASIC (i.e. ATTR) while +others are more standard to FREE BASIC (csrlin or pos) + diff --git a/src/parsetab/tabs.dbm.bak b/src/parsetab/tabs.dbm.bak index 881659c7d..286dd8adf 100644 --- a/src/parsetab/tabs.dbm.bak +++ b/src/parsetab/tabs.dbm.bak @@ -1,4 +1,4 @@ -'zxbpp', (0, 71563) -'asmparse', (71680, 234798) -'zxnext_asmparse', (306688, 259879) -'zxbparser', (566784, 641214) +'zxbpp', (0, 71563) +'asmparse', (71680, 234798) +'zxnext_asmparse', (306688, 259879) +'zxbparser', (566784, 641214) diff --git a/src/parsetab/tabs.dbm.dir b/src/parsetab/tabs.dbm.dir index 881659c7d..286dd8adf 100644 --- a/src/parsetab/tabs.dbm.dir +++ b/src/parsetab/tabs.dbm.dir @@ -1,4 +1,4 @@ -'zxbpp', (0, 71563) -'asmparse', (71680, 234798) -'zxnext_asmparse', (306688, 259879) -'zxbparser', (566784, 641214) +'zxbpp', (0, 71563) +'asmparse', (71680, 234798) +'zxnext_asmparse', (306688, 259879) +'zxbparser', (566784, 641214) diff --git a/src/zxbpp/zxbpp.py b/src/zxbpp/zxbpp.py index 2a81d8024..9339e4d8c 100755 --- a/src/zxbpp/zxbpp.py +++ b/src/zxbpp/zxbpp.py @@ -171,9 +171,7 @@ def set_include_path(): # zx81sd paths take priority: its own files shadow zx48k equivalents. if "zx81sd" in INCLUDE_MAP: zx48k_pwd = get_include_path("zx48k") - INCLUDE_MAP["zx81sd"].extend( - [os.path.join(zx48k_pwd, "stdlib"), os.path.join(zx48k_pwd, "runtime")] - ) + INCLUDE_MAP["zx81sd"].extend([os.path.join(zx48k_pwd, "stdlib"), os.path.join(zx48k_pwd, "runtime")]) INCLUDEPATH = INCLUDE_MAP.get(config.OPTIONS.architecture, []) diff --git a/tests/functional/Makefile b/tests/functional/Makefile index 55a0ed282..88b37ca88 100644 --- a/tests/functional/Makefile +++ b/tests/functional/Makefile @@ -1,41 +1,41 @@ -# vim:noet:ts=4: -.PHONY: test test_ prepro asm bas all - -all: test test_ - -diffbas: - ./test.py -d '**/*.bas' - -test: prepro bin asm bas - -test_: - pytest . -n auto --no-cov -k "cmdline" - -prepro: - ./test.py zxbpp/*.bi - -asm: - ./test.py asm/*.asm - -# This includes all .bas BASIC programs having a corresponding .asm -bas: - ./test.py '**/*.bas' - -# This only includes all .bas BASIC programs having a -# corresponding .asm which starts by a digit. -basic_tests: - ./test.py '**/[0-9]*.bas' - -bin: - ./test.py '**/tzx_*.bas' '**/tap_*.bas' '**/sna_*.bas' '**/z80_*.bas' - -# Measures coverage using only basic tests -.PHONY: basic_coverage -basic_coverage: - ./coverage.sh [0-9]*.bas - coverage html -d basic_coverage - -.PHONY: coverage -coverage: - ./coverage.sh *.bas - coverage html -d coverage +# vim:noet:ts=4: +.PHONY: test test_ prepro asm bas all + +all: test test_ + +diffbas: + ./test.py -d '**/*.bas' + +test: prepro bin asm bas + +test_: + pytest . -n auto --no-cov -k "cmdline" + +prepro: + ./test.py zxbpp/*.bi + +asm: + ./test.py asm/*.asm + +# This includes all .bas BASIC programs having a corresponding .asm +bas: + ./test.py '**/*.bas' + +# This only includes all .bas BASIC programs having a +# corresponding .asm which starts by a digit. +basic_tests: + ./test.py '**/[0-9]*.bas' + +bin: + ./test.py '**/tzx_*.bas' '**/tap_*.bas' '**/sna_*.bas' '**/z80_*.bas' + +# Measures coverage using only basic tests +.PHONY: basic_coverage +basic_coverage: + ./coverage.sh [0-9]*.bas + coverage html -d basic_coverage + +.PHONY: coverage +coverage: + ./coverage.sh *.bas + coverage html -d coverage diff --git a/tests/functional/arch/zx48k/border00_IC.ic b/tests/functional/arch/zx48k/border00_IC.ic index 14b733047..7031602f2 100644 --- a/tests/functional/arch/zx48k/border00_IC.ic +++ b/tests/functional/arch/zx48k/border00_IC.ic @@ -1,4 +1,4 @@ -('fparamu8', '7') -('call', '.core.BORDER', '0') -('end', '0') -('inline', ';; --- end of user code ---') +('fparamu8', '7') +('call', '.core.BORDER', '0') +('end', '0') +('inline', ';; --- end of user code ---') diff --git a/tests/functional/arch/zx48k/func_call_IC.ic b/tests/functional/arch/zx48k/func_call_IC.ic index dbee01323..b061fa1b3 100644 --- a/tests/functional/arch/zx48k/func_call_IC.ic +++ b/tests/functional/arch/zx48k/func_call_IC.ic @@ -1,17 +1,17 @@ -('paramf', '0.0') -('call', '_test', '0') -('call', '.core.__MEM_FREE', '0') -('end', '0') -('label', '_test') -('enter', '0') -('ploadf', 't3', '0') -('jzerof', 't3', '.LABEL.__LABEL1') -('retstr', '#.LABEL.__LABEL2', '_test__leave') -('label', '.LABEL.__LABEL1') -('inline', '#line 3 "func_call_IC.bas"') -('inline', '\nld hl, 0\n') -('inline', '#line 6 "func_call_IC.bas"') -('label', '_test__leave') -('leave', '6') -('vard', '.LABEL.__LABEL2', "['0008', '6E', '6F', '74', '20', '7A', '65', '72', '6F']") -('inline', ';; --- end of user code ---') +('paramf', '0.0') +('call', '_test', '0') +('call', '.core.__MEM_FREE', '0') +('end', '0') +('label', '_test') +('enter', '0') +('ploadf', 't3', '0') +('jzerof', 't3', '.LABEL.__LABEL1') +('retstr', '#.LABEL.__LABEL2', '_test__leave') +('label', '.LABEL.__LABEL1') +('inline', '#line 3 "func_call_IC.bas"') +('inline', '\nld hl, 0\n') +('inline', '#line 6 "func_call_IC.bas"') +('label', '_test__leave') +('leave', '6') +('vard', '.LABEL.__LABEL2', "['0008', '6E', '6F', '74', '20', '7A', '65', '72', '6F']") +('inline', ';; --- end of user code ---') diff --git a/tests/functional/zxbpp/builtin.out b/tests/functional/zxbpp/builtin.out index d0ceec53c..3f41ec510 100644 --- a/tests/functional/zxbpp/builtin.out +++ b/tests/functional/zxbpp/builtin.out @@ -1,28 +1,28 @@ -#line 1 "builtin.bi" - - - -#line 5 "builtin.bi" -#line 6 "builtin.bi" - -"builtin.bi" + "a" 7 + 5 -"builtin.bi" + "a" 8 + 5 - -5 + 1 - -"builtin.bi" -13 - - -#line 17 "builtin.bi" -#line 18 "builtin.bi" - -0 -"a" - -5 + 1 - -#line 25 "builtin.bi" -5 + 1 - -0 + "a" "a" + 5 +#line 1 "builtin.bi" + + + +#line 5 "builtin.bi" +#line 6 "builtin.bi" + +"builtin.bi" + "a" 7 + 5 +"builtin.bi" + "a" 8 + 5 + +5 + 1 + +"builtin.bi" +13 + + +#line 17 "builtin.bi" +#line 18 "builtin.bi" + +0 +"a" + +5 + 1 + +#line 25 "builtin.bi" +5 + 1 + +0 + "a" "a" + 5 diff --git a/tests/functional/zxbpp/emook0.out b/tests/functional/zxbpp/emook0.out index b04b111e6..a297f8dae 100644 --- a/tests/functional/zxbpp/emook0.out +++ b/tests/functional/zxbpp/emook0.out @@ -1,9 +1,9 @@ -#line 1 "emook0.bi" - - -ASM - - LD A,A - LD BC,254 -#line 7 -end Asm +#line 1 "emook0.bi" + + +ASM + + LD A,A + LD BC,254 +#line 7 +end Asm diff --git a/tests/functional/zxbpp/iflogic.out b/tests/functional/zxbpp/iflogic.out index e81ffc7be..7e1427ea9 100644 --- a/tests/functional/zxbpp/iflogic.out +++ b/tests/functional/zxbpp/iflogic.out @@ -1,18 +1,18 @@ -#line 1 "iflogic.bi" - -#line 3 "iflogic.bi" -#line 4 "iflogic.bi" - - -PRINT "Or works" -#line 8 "iflogic.bi" - - -PRINT "And works" -#line 12 "iflogic.bi" - - -PRINT "Parenthesis works" -#line 16 "iflogic.bi" - -#line 20 "iflogic.bi" +#line 1 "iflogic.bi" + +#line 3 "iflogic.bi" +#line 4 "iflogic.bi" + + +PRINT "Or works" +#line 8 "iflogic.bi" + + +PRINT "And works" +#line 12 "iflogic.bi" + + +PRINT "Parenthesis works" +#line 16 "iflogic.bi" + +#line 20 "iflogic.bi" diff --git a/tests/functional/zxbpp/init_dot.out b/tests/functional/zxbpp/init_dot.out index 422b07b01..a30ea1f9a 100644 --- a/tests/functional/zxbpp/init_dot.out +++ b/tests/functional/zxbpp/init_dot.out @@ -1,11 +1,11 @@ -#line 1 "init_dot.bi" - -#init "XX.main" - -jp XX.main -push namespace XX -main: - nop - -pop namespace -ret +#line 1 "init_dot.bi" + +#init "XX.main" + +jp XX.main +push namespace XX +main: + nop + +pop namespace +ret diff --git a/tests/functional/zxbpp/line_asm.out b/tests/functional/zxbpp/line_asm.out index bd9d6ec71..f3e4d721e 100644 --- a/tests/functional/zxbpp/line_asm.out +++ b/tests/functional/zxbpp/line_asm.out @@ -1,4 +1,4 @@ -#line 1 "line_asm.bi" -ASM -NOP -END ASM +#line 1 "line_asm.bi" +ASM +NOP +END ASM diff --git a/tests/functional/zxbpp/once.out b/tests/functional/zxbpp/once.out index ddfee5fe1..d639f4c4a 100644 --- a/tests/functional/zxbpp/once.out +++ b/tests/functional/zxbpp/once.out @@ -1,8 +1,8 @@ -#line 1 "once.bi" - - -DIM a as Ubyte - - - -DIM b as Ubyte +#line 1 "once.bi" + + +DIM a as Ubyte + + + +DIM b as Ubyte diff --git a/tests/functional/zxbpp/once_base.out b/tests/functional/zxbpp/once_base.out index 9f0d4c4ab..cbdb6a01b 100644 --- a/tests/functional/zxbpp/once_base.out +++ b/tests/functional/zxbpp/once_base.out @@ -1,12 +1,12 @@ -#line 1 "once_base.bi" - -#line 1 "once.bi" - - -DIM a as Ubyte - - - -DIM b as Ubyte -#line 3 "once_base.bi" - +#line 1 "once_base.bi" + +#line 1 "once.bi" + + +DIM a as Ubyte + + + +DIM b as Ubyte +#line 3 "once_base.bi" + diff --git a/tests/functional/zxbpp/other_arch.out b/tests/functional/zxbpp/other_arch.out index 161311b03..5b6695f33 100644 --- a/tests/functional/zxbpp/other_arch.out +++ b/tests/functional/zxbpp/other_arch.out @@ -1,255 +1,255 @@ -#line 1 "other_arch.bi" -#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" - - - - - - - - - - - -#line 13 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" - -#pragma push(case_insensitive) -#pragma case_insensitive = TRUE - - - - - - - - - - - -function attr(byval row as ubyte, byval col as ubyte) as ubyte - asm - push namespace core - - PROC - LOCAL __ATTR_END - - ld e, (ix+7) - ld d, (ix+5) - - - call __IN_SCREEN - jr nc, __ATTR_END - - call __ATTR_ADDR - ld a, (hl) - -__ATTR_END: - ENDP - - pop namespace - end asm - -end function - - - - - - - - - - - - - - -sub setattr(byval row as ubyte, byval col as ubyte, byval value as ubyte) - asm - push namespace core - - PROC - LOCAL __ATTR_END - - ld e, (ix+7) - ld d, (ix+5) - - - call __IN_SCREEN - jr nc, __ATTR_END - - call __ATTR_ADDR - ld a, (ix+9) - ld (hl), a - -__ATTR_END: - ENDP - - pop namespace - end asm - -end sub - - - - - - - - - - - -function fastcall attraddr(byval row as ubyte, byval col as ubyte) as uinteger - asm - push namespace core - - pop hl - ex (sp), hl - ld d, a - ld e, h - jp __ATTR_ADDR - pop namespace - end asm -end function - - - -#pragma pop(case_insensitive) - - -#require "attr.asm" - - -#require "in_screen.asm" - -#line 125 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" -#line 2 "other_arch.bi" -#line 1 "/zxbasic/src/lib/arch/zxnext/stdlib/hex.bas" - - - - - - - -#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/hex.bas" - - - - - - - - - - - -#line 13 "/zxbasic/src/lib/arch/zx48k/stdlib/hex.bas" - -#pragma push(case_insensitive) -#pragma case_insensitive = TRUE - -#pragma push(string_base) -#pragma string_base = 0 - - - - - - - - - - -function FASTCALL hex(num as ULong) as String - asm - push namespace core - PROC - LOCAL SUB_CHAR - LOCAL SUB_CHAR2 - LOCAL END_CHAR - LOCAL DIGIT - - push hl - push de - ld bc,10 - call __MEM_ALLOC - ld a, h - or l - pop de - pop bc - ret z - - push hl - ld (hl), 8 - inc hl - ld (hl), 0 - inc hl - - call DIGIT - ld d, e - call DIGIT - ld d, b - call DIGIT - ld d, c - call DIGIT - pop hl - ret - -DIGIT: - ld a, d - call SUB_CHAR - ld a, d - jr SUB_CHAR2 - -SUB_CHAR: - rrca - rrca - rrca - rrca - -SUB_CHAR2: - and 0Fh - add a, '0' - cp '9' + 1 - jr c, END_CHAR - add a, 7 - -END_CHAR: - ld (hl), a - inc hl - ret - - ENDP - pop namespace - end asm -end function - - - -function hex16(n as UInteger) as String - Dim a$ as String - a$ = hex(n) - return a$(4 TO 7) -end function - - - -Function hex8 (n as UByte) as String - Dim res$ as String - - res$ = hex(n) - return res$(6 TO 7) -end function - - -#pragma pop(string_base) -#pragma pop(case_insensitive) - - -#require "mem/alloc.asm" - -#line 118 "/zxbasic/src/lib/arch/zx48k/stdlib/hex.bas" -#line 9 "/zxbasic/src/lib/arch/zxnext/stdlib/hex.bas" -#line 3 "other_arch.bi" +#line 1 "other_arch.bi" +#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" + + + + + + + + + + + +#line 13 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" + +#pragma push(case_insensitive) +#pragma case_insensitive = TRUE + + + + + + + + + + + +function attr(byval row as ubyte, byval col as ubyte) as ubyte + asm + push namespace core + + PROC + LOCAL __ATTR_END + + ld e, (ix+7) + ld d, (ix+5) + + + call __IN_SCREEN + jr nc, __ATTR_END + + call __ATTR_ADDR + ld a, (hl) + +__ATTR_END: + ENDP + + pop namespace + end asm + +end function + + + + + + + + + + + + + + +sub setattr(byval row as ubyte, byval col as ubyte, byval value as ubyte) + asm + push namespace core + + PROC + LOCAL __ATTR_END + + ld e, (ix+7) + ld d, (ix+5) + + + call __IN_SCREEN + jr nc, __ATTR_END + + call __ATTR_ADDR + ld a, (ix+9) + ld (hl), a + +__ATTR_END: + ENDP + + pop namespace + end asm + +end sub + + + + + + + + + + + +function fastcall attraddr(byval row as ubyte, byval col as ubyte) as uinteger + asm + push namespace core + + pop hl + ex (sp), hl + ld d, a + ld e, h + jp __ATTR_ADDR + pop namespace + end asm +end function + + + +#pragma pop(case_insensitive) + + +#require "attr.asm" + + +#require "in_screen.asm" + +#line 125 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" +#line 2 "other_arch.bi" +#line 1 "/zxbasic/src/lib/arch/zxnext/stdlib/hex.bas" + + + + + + + +#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/hex.bas" + + + + + + + + + + + +#line 13 "/zxbasic/src/lib/arch/zx48k/stdlib/hex.bas" + +#pragma push(case_insensitive) +#pragma case_insensitive = TRUE + +#pragma push(string_base) +#pragma string_base = 0 + + + + + + + + + + +function FASTCALL hex(num as ULong) as String + asm + push namespace core + PROC + LOCAL SUB_CHAR + LOCAL SUB_CHAR2 + LOCAL END_CHAR + LOCAL DIGIT + + push hl + push de + ld bc,10 + call __MEM_ALLOC + ld a, h + or l + pop de + pop bc + ret z + + push hl + ld (hl), 8 + inc hl + ld (hl), 0 + inc hl + + call DIGIT + ld d, e + call DIGIT + ld d, b + call DIGIT + ld d, c + call DIGIT + pop hl + ret + +DIGIT: + ld a, d + call SUB_CHAR + ld a, d + jr SUB_CHAR2 + +SUB_CHAR: + rrca + rrca + rrca + rrca + +SUB_CHAR2: + and 0Fh + add a, '0' + cp '9' + 1 + jr c, END_CHAR + add a, 7 + +END_CHAR: + ld (hl), a + inc hl + ret + + ENDP + pop namespace + end asm +end function + + + +function hex16(n as UInteger) as String + Dim a$ as String + a$ = hex(n) + return a$(4 TO 7) +end function + + + +Function hex8 (n as UByte) as String + Dim res$ as String + + res$ = hex(n) + return res$(6 TO 7) +end function + + +#pragma pop(string_base) +#pragma pop(case_insensitive) + + +#require "mem/alloc.asm" + +#line 118 "/zxbasic/src/lib/arch/zx48k/stdlib/hex.bas" +#line 9 "/zxbasic/src/lib/arch/zxnext/stdlib/hex.bas" +#line 3 "other_arch.bi" diff --git a/tests/functional/zxbpp/prepro00.out b/tests/functional/zxbpp/prepro00.out index 7b2942837..af0893179 100644 --- a/tests/functional/zxbpp/prepro00.out +++ b/tests/functional/zxbpp/prepro00.out @@ -1,128 +1,128 @@ -#line 1 "prepro00.bi" -#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" - - - - - - - - - - - -#line 13 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" - -#pragma push(case_insensitive) -#pragma case_insensitive = TRUE - - - - - - - - - - - -function attr(byval row as ubyte, byval col as ubyte) as ubyte - asm - push namespace core - - PROC - LOCAL __ATTR_END - - ld e, (ix+7) - ld d, (ix+5) - - - call __IN_SCREEN - jr nc, __ATTR_END - - call __ATTR_ADDR - ld a, (hl) - -__ATTR_END: - ENDP - - pop namespace - end asm - -end function - - - - - - - - - - - - - - -sub setattr(byval row as ubyte, byval col as ubyte, byval value as ubyte) - asm - push namespace core - - PROC - LOCAL __ATTR_END - - ld e, (ix+7) - ld d, (ix+5) - - - call __IN_SCREEN - jr nc, __ATTR_END - - call __ATTR_ADDR - ld a, (ix+9) - ld (hl), a - -__ATTR_END: - ENDP - - pop namespace - end asm - -end sub - - - - - - - - - - - -function fastcall attraddr(byval row as ubyte, byval col as ubyte) as uinteger - asm - push namespace core - - pop hl - ex (sp), hl - ld d, a - ld e, h - jp __ATTR_ADDR - pop namespace - end asm -end function - - - -#pragma pop(case_insensitive) - - -#require "attr.asm" - - -#require "in_screen.asm" - -#line 125 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" -#line 2 "prepro00.bi" -PRINT "HELLO" +#line 1 "prepro00.bi" +#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" + + + + + + + + + + + +#line 13 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" + +#pragma push(case_insensitive) +#pragma case_insensitive = TRUE + + + + + + + + + + + +function attr(byval row as ubyte, byval col as ubyte) as ubyte + asm + push namespace core + + PROC + LOCAL __ATTR_END + + ld e, (ix+7) + ld d, (ix+5) + + + call __IN_SCREEN + jr nc, __ATTR_END + + call __ATTR_ADDR + ld a, (hl) + +__ATTR_END: + ENDP + + pop namespace + end asm + +end function + + + + + + + + + + + + + + +sub setattr(byval row as ubyte, byval col as ubyte, byval value as ubyte) + asm + push namespace core + + PROC + LOCAL __ATTR_END + + ld e, (ix+7) + ld d, (ix+5) + + + call __IN_SCREEN + jr nc, __ATTR_END + + call __ATTR_ADDR + ld a, (ix+9) + ld (hl), a + +__ATTR_END: + ENDP + + pop namespace + end asm + +end sub + + + + + + + + + + + +function fastcall attraddr(byval row as ubyte, byval col as ubyte) as uinteger + asm + push namespace core + + pop hl + ex (sp), hl + ld d, a + ld e, h + jp __ATTR_ADDR + pop namespace + end asm +end function + + + +#pragma pop(case_insensitive) + + +#require "attr.asm" + + +#require "in_screen.asm" + +#line 125 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" +#line 2 "prepro00.bi" +PRINT "HELLO" diff --git a/tests/functional/zxbpp/prepro01.out b/tests/functional/zxbpp/prepro01.out index 38fe92ced..bcc03a728 100644 --- a/tests/functional/zxbpp/prepro01.out +++ b/tests/functional/zxbpp/prepro01.out @@ -1,129 +1,129 @@ -#line 1 "prepro01.bi" -#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" - - - - - - - - - - - -#line 13 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" - -#pragma push(case_insensitive) -#pragma case_insensitive = TRUE - - - - - - - - - - - -function attr(byval row as ubyte, byval col as ubyte) as ubyte - asm - push namespace core - - PROC - LOCAL __ATTR_END - - ld e, (ix+7) - ld d, (ix+5) - - - call __IN_SCREEN - jr nc, __ATTR_END - - call __ATTR_ADDR - ld a, (hl) - -__ATTR_END: - ENDP - - pop namespace - end asm - -end function - - - - - - - - - - - - - - -sub setattr(byval row as ubyte, byval col as ubyte, byval value as ubyte) - asm - push namespace core - - PROC - LOCAL __ATTR_END - - ld e, (ix+7) - ld d, (ix+5) - - - call __IN_SCREEN - jr nc, __ATTR_END - - call __ATTR_ADDR - ld a, (ix+9) - ld (hl), a - -__ATTR_END: - ENDP - - pop namespace - end asm - -end sub - - - - - - - - - - - -function fastcall attraddr(byval row as ubyte, byval col as ubyte) as uinteger - asm - push namespace core - - pop hl - ex (sp), hl - ld d, a - ld e, h - jp __ATTR_ADDR - pop namespace - end asm -end function - - - -#pragma pop(case_insensitive) - - -#require "attr.asm" - - -#require "in_screen.asm" - -#line 125 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" -#line 2 "prepro01.bi" - -PRINT "HOLA" +#line 1 "prepro01.bi" +#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" + + + + + + + + + + + +#line 13 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" + +#pragma push(case_insensitive) +#pragma case_insensitive = TRUE + + + + + + + + + + + +function attr(byval row as ubyte, byval col as ubyte) as ubyte + asm + push namespace core + + PROC + LOCAL __ATTR_END + + ld e, (ix+7) + ld d, (ix+5) + + + call __IN_SCREEN + jr nc, __ATTR_END + + call __ATTR_ADDR + ld a, (hl) + +__ATTR_END: + ENDP + + pop namespace + end asm + +end function + + + + + + + + + + + + + + +sub setattr(byval row as ubyte, byval col as ubyte, byval value as ubyte) + asm + push namespace core + + PROC + LOCAL __ATTR_END + + ld e, (ix+7) + ld d, (ix+5) + + + call __IN_SCREEN + jr nc, __ATTR_END + + call __ATTR_ADDR + ld a, (ix+9) + ld (hl), a + +__ATTR_END: + ENDP + + pop namespace + end asm + +end sub + + + + + + + + + + + +function fastcall attraddr(byval row as ubyte, byval col as ubyte) as uinteger + asm + push namespace core + + pop hl + ex (sp), hl + ld d, a + ld e, h + jp __ATTR_ADDR + pop namespace + end asm +end function + + + +#pragma pop(case_insensitive) + + +#require "attr.asm" + + +#require "in_screen.asm" + +#line 125 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" +#line 2 "prepro01.bi" + +PRINT "HOLA" diff --git a/tests/functional/zxbpp/prepro02.out b/tests/functional/zxbpp/prepro02.out index 68aa48ce2..3c3d5c7b9 100644 --- a/tests/functional/zxbpp/prepro02.out +++ b/tests/functional/zxbpp/prepro02.out @@ -1,8 +1,8 @@ -#line 1 "prepro02.bi" - - - - -#line 6 "prepro02.bi" - - +#line 1 "prepro02.bi" + + + + +#line 6 "prepro02.bi" + + diff --git a/tests/functional/zxbpp/prepro03.out b/tests/functional/zxbpp/prepro03.out index 29c0d0931..c4b2fe796 100644 --- a/tests/functional/zxbpp/prepro03.out +++ b/tests/functional/zxbpp/prepro03.out @@ -1,9 +1,9 @@ -#line 1 "prepro03.bi" - -#line 4 "prepro03.bi" -#line 5 "prepro03.bi" - -x - this line must be compiled -#line 7 -y +#line 1 "prepro03.bi" + +#line 4 "prepro03.bi" +#line 5 "prepro03.bi" + +x + this line must be compiled +#line 7 +y diff --git a/tests/functional/zxbpp/prepro04.out b/tests/functional/zxbpp/prepro04.out index 99b4448a6..ac005c346 100644 --- a/tests/functional/zxbpp/prepro04.out +++ b/tests/functional/zxbpp/prepro04.out @@ -1,6 +1,6 @@ -#line 1 "prepro04.bi" - -#line 5 "prepro04.bi" - -#line 7 "prepro04.bi" - +#line 1 "prepro04.bi" + +#line 5 "prepro04.bi" + +#line 7 "prepro04.bi" + diff --git a/tests/functional/zxbpp/prepro05.out b/tests/functional/zxbpp/prepro05.out index 31c4c4012..002842a64 100644 --- a/tests/functional/zxbpp/prepro05.out +++ b/tests/functional/zxbpp/prepro05.out @@ -1,7 +1,7 @@ -#line 1 "prepro05.bi" - -#line 3 "prepro05.bi" -#line 4 "prepro05.bi" - -y -test2 +#line 1 "prepro05.bi" + +#line 3 "prepro05.bi" +#line 4 "prepro05.bi" + +y +test2 diff --git a/tests/functional/zxbpp/prepro06.out b/tests/functional/zxbpp/prepro06.out index ec1f8f52d..a1adfc2fd 100644 --- a/tests/functional/zxbpp/prepro06.out +++ b/tests/functional/zxbpp/prepro06.out @@ -1,6 +1,6 @@ -#line 1 "prepro06.bi" - -#line 3 "prepro06.bi" - - -func () +#line 1 "prepro06.bi" + +#line 3 "prepro06.bi" + + +func () diff --git a/tests/functional/zxbpp/prepro09.out b/tests/functional/zxbpp/prepro09.out index b39bdec97..5eed85abe 100644 --- a/tests/functional/zxbpp/prepro09.out +++ b/tests/functional/zxbpp/prepro09.out @@ -1,4 +1,4 @@ -#line 1 "prepro09.bi" - - - +#line 1 "prepro09.bi" + + + diff --git a/tests/functional/zxbpp/prepro10.out b/tests/functional/zxbpp/prepro10.out index b5f7a2435..2ca0d7a86 100644 --- a/tests/functional/zxbpp/prepro10.out +++ b/tests/functional/zxbpp/prepro10.out @@ -1,6 +1,6 @@ -#line 1 "prepro10.bi" - - -function test(byval a as byte) - let test = 0 -end function +#line 1 "prepro10.bi" + + +function test(byval a as byte) + let test = 0 +end function diff --git a/tests/functional/zxbpp/prepro11.out b/tests/functional/zxbpp/prepro11.out index 58a87d63f..16d1cda9f 100644 --- a/tests/functional/zxbpp/prepro11.out +++ b/tests/functional/zxbpp/prepro11.out @@ -1,8 +1,8 @@ -#line 1 "prepro11.bi" - - - -#line 5 "prepro11.bi" -#line 6 "prepro11.bi" - -PRINT "HELLO WORLD" +#line 1 "prepro11.bi" + + + +#line 5 "prepro11.bi" +#line 6 "prepro11.bi" + +PRINT "HELLO WORLD" diff --git a/tests/functional/zxbpp/prepro12.out b/tests/functional/zxbpp/prepro12.out index 8a9dc287a..1847f6a1a 100644 --- a/tests/functional/zxbpp/prepro12.out +++ b/tests/functional/zxbpp/prepro12.out @@ -1,8 +1,8 @@ -#line 1 "prepro12.bi" - - - -#line 5 "prepro12.bi" -#line 6 "prepro12.bi" - -)PRINT "HELLO WORLD" +#line 1 "prepro12.bi" + + + +#line 5 "prepro12.bi" +#line 6 "prepro12.bi" + +)PRINT "HELLO WORLD" diff --git a/tests/functional/zxbpp/prepro13.out b/tests/functional/zxbpp/prepro13.out index 149ca4c6f..0ed5de8bc 100644 --- a/tests/functional/zxbpp/prepro13.out +++ b/tests/functional/zxbpp/prepro13.out @@ -1,8 +1,8 @@ -#line 1 "prepro13.bi" - - - -#line 5 "prepro13.bi" -#line 6 "prepro13.bi" - -PRINT "HELLO WORLD" +#line 1 "prepro13.bi" + + + +#line 5 "prepro13.bi" +#line 6 "prepro13.bi" + +PRINT "HELLO WORLD" diff --git a/tests/functional/zxbpp/prepro14.out b/tests/functional/zxbpp/prepro14.out index 6b2b9e78d..d7e474e95 100644 --- a/tests/functional/zxbpp/prepro14.out +++ b/tests/functional/zxbpp/prepro14.out @@ -1,9 +1,9 @@ -#line 1 "prepro14.bi" - - - -#line 5 "prepro14.bi" -#line 6 "prepro14.bi" -#line 7 "prepro14.bi" - -PRINT "HELLO WORLD" +#line 1 "prepro14.bi" + + + +#line 5 "prepro14.bi" +#line 6 "prepro14.bi" +#line 7 "prepro14.bi" + +PRINT "HELLO WORLD" diff --git a/tests/functional/zxbpp/prepro15.out b/tests/functional/zxbpp/prepro15.out index a57b86312..76f4d4747 100644 --- a/tests/functional/zxbpp/prepro15.out +++ b/tests/functional/zxbpp/prepro15.out @@ -1,12 +1,12 @@ -#line 1 "prepro15.bi" - - - - - - -#line 8 "prepro15.bi" - -PRINT 10; -PRINT "Hello"; -macro +#line 1 "prepro15.bi" + + + + + + +#line 8 "prepro15.bi" + +PRINT 10; +PRINT "Hello"; +macro diff --git a/tests/functional/zxbpp/prepro16.out b/tests/functional/zxbpp/prepro16.out index 8da46a07b..1e616d55f 100644 --- a/tests/functional/zxbpp/prepro16.out +++ b/tests/functional/zxbpp/prepro16.out @@ -1,11 +1,11 @@ -#line 1 "prepro16.bi" - - - - - -#line 7 "prepro16.bi" -#line 8 "prepro16.bi" - -PRINT 20; -PRINT 10 + 20; +#line 1 "prepro16.bi" + + + + + +#line 7 "prepro16.bi" +#line 8 "prepro16.bi" + +PRINT 20; +PRINT 10 + 20; diff --git a/tests/functional/zxbpp/prepro17.out b/tests/functional/zxbpp/prepro17.out index b3ad6e2ed..80349359a 100644 --- a/tests/functional/zxbpp/prepro17.out +++ b/tests/functional/zxbpp/prepro17.out @@ -1,8 +1,8 @@ -#line 1 "prepro17.bi" - - - - -#line 6 "prepro17.bi" - -PRINT PRINT PRINT 10;;; +#line 1 "prepro17.bi" + + + + +#line 6 "prepro17.bi" + +PRINT PRINT PRINT 10;;; diff --git a/tests/functional/zxbpp/prepro18.out b/tests/functional/zxbpp/prepro18.out index 7fced6439..268595dbb 100644 --- a/tests/functional/zxbpp/prepro18.out +++ b/tests/functional/zxbpp/prepro18.out @@ -1,7 +1,7 @@ -#line 1 "prepro18.bi" - - - -#line 5 "prepro18.bi" - -PRINT (10, 20); +#line 1 "prepro18.bi" + + + +#line 5 "prepro18.bi" + +PRINT (10, 20); diff --git a/tests/functional/zxbpp/prepro19.out b/tests/functional/zxbpp/prepro19.out index 7dde492a3..9dcd02cf3 100644 --- a/tests/functional/zxbpp/prepro19.out +++ b/tests/functional/zxbpp/prepro19.out @@ -1,7 +1,7 @@ -#line 1 "prepro19.bi" - - - -#line 5 "prepro19.bi" - -PRINT (10 + PRINT 20;); +#line 1 "prepro19.bi" + + + +#line 5 "prepro19.bi" + +PRINT (10 + PRINT 20;); diff --git a/tests/functional/zxbpp/prepro20.out b/tests/functional/zxbpp/prepro20.out index ea97c3727..1d74d1c0a 100644 --- a/tests/functional/zxbpp/prepro20.out +++ b/tests/functional/zxbpp/prepro20.out @@ -1,7 +1,7 @@ -#line 1 "prepro20.bi" - - - -#line 5 "prepro20.bi" - -PRINT ;) +#line 1 "prepro20.bi" + + + +#line 5 "prepro20.bi" + +PRINT ;) diff --git a/tests/functional/zxbpp/prepro21.out b/tests/functional/zxbpp/prepro21.out index dda7d1be9..094ff345e 100644 --- a/tests/functional/zxbpp/prepro21.out +++ b/tests/functional/zxbpp/prepro21.out @@ -1,8 +1,8 @@ -#line 1 "prepro21.bi" - - - -#line 5 "prepro21.bi" -#line 6 "prepro21.bi" - -10 +#line 1 "prepro21.bi" + + + +#line 5 "prepro21.bi" +#line 6 "prepro21.bi" + +10 diff --git a/tests/functional/zxbpp/prepro23.out b/tests/functional/zxbpp/prepro23.out index 2ae9cd2cf..f091e4e73 100644 --- a/tests/functional/zxbpp/prepro23.out +++ b/tests/functional/zxbpp/prepro23.out @@ -1,9 +1,9 @@ -#line 1 "prepro23.bi" - - - -#line 5 "prepro23.bi" -#line 6 "prepro23.bi" -#line 7 "prepro23.bi" - -PRINT 10; +#line 1 "prepro23.bi" + + + +#line 5 "prepro23.bi" +#line 6 "prepro23.bi" +#line 7 "prepro23.bi" + +PRINT 10; diff --git a/tests/functional/zxbpp/prepro24.out b/tests/functional/zxbpp/prepro24.out index d24b609e0..3d82bdadd 100644 --- a/tests/functional/zxbpp/prepro24.out +++ b/tests/functional/zxbpp/prepro24.out @@ -1,11 +1,11 @@ -#line 1 "prepro24.bi" - - -#line 6 "prepro24.bi" - - -FOR xxx = 1 TO 10 - PRINT xxx - NEXT -#line 9 -PRINT "Line 9" +#line 1 "prepro24.bi" + + +#line 6 "prepro24.bi" + + +FOR xxx = 1 TO 10 + PRINT xxx + NEXT +#line 9 +PRINT "Line 9" diff --git a/tests/functional/zxbpp/prepro25.out b/tests/functional/zxbpp/prepro25.out index c3026c9cf..f8f76e628 100644 --- a/tests/functional/zxbpp/prepro25.out +++ b/tests/functional/zxbpp/prepro25.out @@ -1,7 +1,7 @@ -#line 1 "prepro25.bi" - - - -#line 5 "prepro25.bi" - -(x + y) * 2 +#line 1 "prepro25.bi" + + + +#line 5 "prepro25.bi" + +(x + y) * 2 diff --git a/tests/functional/zxbpp/prepro26.out b/tests/functional/zxbpp/prepro26.out index efb28cc9e..0065c88eb 100644 --- a/tests/functional/zxbpp/prepro26.out +++ b/tests/functional/zxbpp/prepro26.out @@ -1,7 +1,7 @@ -#line 1 "prepro26.bi" - - - -#line 5 "prepro26.bi" - -(x + y) + 4 * 2 +#line 1 "prepro26.bi" + + + +#line 5 "prepro26.bi" + +(x + y) + 4 * 2 diff --git a/tests/functional/zxbpp/prepro27.out b/tests/functional/zxbpp/prepro27.out index 2d9cbb470..79c511a1a 100644 --- a/tests/functional/zxbpp/prepro27.out +++ b/tests/functional/zxbpp/prepro27.out @@ -1,14 +1,14 @@ -#line 1 "prepro27.bi" -Sub x -End Sub - - - - -#line 1 "prepro27.bi" -Sub x -End Sub - -#line 9 "prepro27.bi" -#line 8 "prepro27.bi" -#line 9 "prepro27.bi" +#line 1 "prepro27.bi" +Sub x +End Sub + + + + +#line 1 "prepro27.bi" +Sub x +End Sub + +#line 9 "prepro27.bi" +#line 8 "prepro27.bi" +#line 9 "prepro27.bi" diff --git a/tests/functional/zxbpp/prepro29.out b/tests/functional/zxbpp/prepro29.out index 0dd7ce49b..5f43a94c2 100644 --- a/tests/functional/zxbpp/prepro29.out +++ b/tests/functional/zxbpp/prepro29.out @@ -1,4 +1,4 @@ -#line 1 "prepro29.bi" - - -ZZ +#line 1 "prepro29.bi" + + +ZZ diff --git a/tests/functional/zxbpp/prepro30.out b/tests/functional/zxbpp/prepro30.out index 4de9e4752..02c39216c 100644 --- a/tests/functional/zxbpp/prepro30.out +++ b/tests/functional/zxbpp/prepro30.out @@ -1,128 +1,128 @@ -#line 1 "prepro30.bi" -#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" - - - - - - - - - - - -#line 13 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" - -#pragma push(case_insensitive) -#pragma case_insensitive = TRUE - - - - - - - - - - - -function attr(byval row as ubyte, byval col as ubyte) as ubyte - asm - push namespace core - - PROC - LOCAL __ATTR_END - - ld e, (ix+7) - ld d, (ix+5) - - - call __IN_SCREEN - jr nc, __ATTR_END - - call __ATTR_ADDR - ld a, (hl) - -__ATTR_END: - ENDP - - pop namespace - end asm - -end function - - - - - - - - - - - - - - -sub setattr(byval row as ubyte, byval col as ubyte, byval value as ubyte) - asm - push namespace core - - PROC - LOCAL __ATTR_END - - ld e, (ix+7) - ld d, (ix+5) - - - call __IN_SCREEN - jr nc, __ATTR_END - - call __ATTR_ADDR - ld a, (ix+9) - ld (hl), a - -__ATTR_END: - ENDP - - pop namespace - end asm - -end sub - - - - - - - - - - - -function fastcall attraddr(byval row as ubyte, byval col as ubyte) as uinteger - asm - push namespace core - - pop hl - ex (sp), hl - ld d, a - ld e, h - jp __ATTR_ADDR - pop namespace - end asm -end function - - - -#pragma pop(case_insensitive) - - -#require "attr.asm" - - -#require "in_screen.asm" - -#line 125 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" -#line 2 "prepro30.bi" - +#line 1 "prepro30.bi" +#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" + + + + + + + + + + + +#line 13 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" + +#pragma push(case_insensitive) +#pragma case_insensitive = TRUE + + + + + + + + + + + +function attr(byval row as ubyte, byval col as ubyte) as ubyte + asm + push namespace core + + PROC + LOCAL __ATTR_END + + ld e, (ix+7) + ld d, (ix+5) + + + call __IN_SCREEN + jr nc, __ATTR_END + + call __ATTR_ADDR + ld a, (hl) + +__ATTR_END: + ENDP + + pop namespace + end asm + +end function + + + + + + + + + + + + + + +sub setattr(byval row as ubyte, byval col as ubyte, byval value as ubyte) + asm + push namespace core + + PROC + LOCAL __ATTR_END + + ld e, (ix+7) + ld d, (ix+5) + + + call __IN_SCREEN + jr nc, __ATTR_END + + call __ATTR_ADDR + ld a, (ix+9) + ld (hl), a + +__ATTR_END: + ENDP + + pop namespace + end asm + +end sub + + + + + + + + + + + +function fastcall attraddr(byval row as ubyte, byval col as ubyte) as uinteger + asm + push namespace core + + pop hl + ex (sp), hl + ld d, a + ld e, h + jp __ATTR_ADDR + pop namespace + end asm +end function + + + +#pragma pop(case_insensitive) + + +#require "attr.asm" + + +#require "in_screen.asm" + +#line 125 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" +#line 2 "prepro30.bi" + diff --git a/tests/functional/zxbpp/prepro31.out b/tests/functional/zxbpp/prepro31.out index 69152e43d..b3aca512a 100644 --- a/tests/functional/zxbpp/prepro31.out +++ b/tests/functional/zxbpp/prepro31.out @@ -1,4 +1,4 @@ -#line 1 "prepro31.bi" - - -t(x, (y - 1)) +#line 1 "prepro31.bi" + + +t(x, (y - 1)) diff --git a/tests/functional/zxbpp/prepro32.out b/tests/functional/zxbpp/prepro32.out index 075b19cb5..ac0fc82c3 100644 --- a/tests/functional/zxbpp/prepro32.out +++ b/tests/functional/zxbpp/prepro32.out @@ -1,14 +1,14 @@ -#line 1 "prepro32.bi" -DIM a As Ubyte = 1 - -#line 10 "prepro32.bi" - -PRINT "HELLO WORLD" - - PRINT a; " MACRO START" - ASM - ld hl, _a - inc (hl) - END ASM - PRINT a; " MACRO END" -#line 13 +#line 1 "prepro32.bi" +DIM a As Ubyte = 1 + +#line 10 "prepro32.bi" + +PRINT "HELLO WORLD" + + PRINT a; " MACRO START" + ASM + ld hl, _a + inc (hl) + END ASM + PRINT a; " MACRO END" +#line 13 diff --git a/tests/functional/zxbpp/prepro33.out b/tests/functional/zxbpp/prepro33.out index 8de3545f3..3617a96f7 100644 --- a/tests/functional/zxbpp/prepro33.out +++ b/tests/functional/zxbpp/prepro33.out @@ -1,35 +1,35 @@ -#line 1 "prepro33.bi" -asm - EX DE,HL - PUSH DE - - LD L,(IX+8) - LD H,(IX+9) - EX AF,AF' - LD A,4 - EX AF,AF' - BLPutCharLoop: - LD B,8 - - BLPutCharOneCharLoop: - XOR A - LD C,A - - - LDI - LDI - LDI - LDI - LD A,C - ADD A,E - LD E,A - INC D - LD A,B - OR A - JP NZ,BLPutCharOneCharLoop - - EX AF,AF' - DEC A - JR Z, BLPutCharsEnd - EX AF,AF' -end asm +#line 1 "prepro33.bi" +asm + EX DE,HL + PUSH DE + + LD L,(IX+8) + LD H,(IX+9) + EX AF,AF' + LD A,4 + EX AF,AF' + BLPutCharLoop: + LD B,8 + + BLPutCharOneCharLoop: + XOR A + LD C,A + + + LDI + LDI + LDI + LDI + LD A,C + ADD A,E + LD E,A + INC D + LD A,B + OR A + JP NZ,BLPutCharOneCharLoop + + EX AF,AF' + DEC A + JR Z, BLPutCharsEnd + EX AF,AF' +end asm diff --git a/tests/functional/zxbpp/prepro34.out b/tests/functional/zxbpp/prepro34.out index 645a33509..2108234f0 100644 --- a/tests/functional/zxbpp/prepro34.out +++ b/tests/functional/zxbpp/prepro34.out @@ -1,5 +1,5 @@ -#line 1 "prepro34.bi" - - -LET a = _ - 5 +#line 1 "prepro34.bi" + + +LET a = _ + 5 diff --git a/tests/functional/zxbpp/prepro36.out b/tests/functional/zxbpp/prepro36.out index ad9a29896..c9c27cc18 100644 --- a/tests/functional/zxbpp/prepro36.out +++ b/tests/functional/zxbpp/prepro36.out @@ -1,4 +1,4 @@ -#line 1 "prepro36.bi" - - - +#line 1 "prepro36.bi" + + + diff --git a/tests/functional/zxbpp/prepro37.out b/tests/functional/zxbpp/prepro37.out index 9412f442d..fcfa1ffab 100644 --- a/tests/functional/zxbpp/prepro37.out +++ b/tests/functional/zxbpp/prepro37.out @@ -1,6 +1,6 @@ -#line 1 "prepro37.bi" - - - - -PRINT +#line 1 "prepro37.bi" + + + + +PRINT diff --git a/tests/functional/zxbpp/prepro38.out b/tests/functional/zxbpp/prepro38.out index 21858f01a..df07706e2 100644 --- a/tests/functional/zxbpp/prepro38.out +++ b/tests/functional/zxbpp/prepro38.out @@ -1,7 +1,7 @@ -#line 1 "prepro38.bi" - -#line 4 "prepro38.bi" - - - Test -#line 6 +#line 1 "prepro38.bi" + +#line 4 "prepro38.bi" + + + Test +#line 6 diff --git a/tests/functional/zxbpp/prepro39.out b/tests/functional/zxbpp/prepro39.out index 42c310b70..2723ffcb1 100644 --- a/tests/functional/zxbpp/prepro39.out +++ b/tests/functional/zxbpp/prepro39.out @@ -1,9 +1,9 @@ -#line 1 "prepro39.bi" - - - -#line 6 "prepro39.bi" - - - PRINT , -#line 8 +#line 1 "prepro39.bi" + + + +#line 6 "prepro39.bi" + + + PRINT , +#line 8 diff --git a/tests/functional/zxbpp/prepro40.out b/tests/functional/zxbpp/prepro40.out index 611dbd187..987bc779c 100644 --- a/tests/functional/zxbpp/prepro40.out +++ b/tests/functional/zxbpp/prepro40.out @@ -1,4 +1,4 @@ -#line 1 "prepro40.bi" - - -macro +#line 1 "prepro40.bi" + + +macro diff --git a/tests/functional/zxbpp/prepro41.out b/tests/functional/zxbpp/prepro41.out index 02670ced4..76c952b31 100644 --- a/tests/functional/zxbpp/prepro41.out +++ b/tests/functional/zxbpp/prepro41.out @@ -1,8 +1,8 @@ -#line 1 "prepro41.bi" - - -10 PAUSE 0 : - asm - call 65012 - end asm : PAUSE 0 -#line 7 +#line 1 "prepro41.bi" + + +10 PAUSE 0 : + asm + call 65012 + end asm : PAUSE 0 +#line 7 diff --git a/tests/functional/zxbpp/prepro42.out b/tests/functional/zxbpp/prepro42.out index bda8844f4..932a27240 100644 --- a/tests/functional/zxbpp/prepro42.out +++ b/tests/functional/zxbpp/prepro42.out @@ -1,4 +1,4 @@ -#line 1 "prepro42.bi" - - -#line 6 "prepro42.bi" +#line 1 "prepro42.bi" + + +#line 6 "prepro42.bi" diff --git a/tests/functional/zxbpp/prepro43.out b/tests/functional/zxbpp/prepro43.out index c77d10e4d..1d5bc3876 100644 --- a/tests/functional/zxbpp/prepro43.out +++ b/tests/functional/zxbpp/prepro43.out @@ -1,6 +1,6 @@ -#line 1 "prepro43.bi" - - - -print 1 -#line 6 "prepro43.bi" +#line 1 "prepro43.bi" + + + +print 1 +#line 6 "prepro43.bi" diff --git a/tests/functional/zxbpp/prepro44.out b/tests/functional/zxbpp/prepro44.out index 62908a59e..12b7f6558 100644 --- a/tests/functional/zxbpp/prepro44.out +++ b/tests/functional/zxbpp/prepro44.out @@ -1,4 +1,4 @@ -#line 1 "prepro44.bi" - - -#line 6 "prepro44.bi" +#line 1 "prepro44.bi" + + +#line 6 "prepro44.bi" diff --git a/tests/functional/zxbpp/prepro45.out b/tests/functional/zxbpp/prepro45.out index f23f903a2..61af4f5a4 100644 --- a/tests/functional/zxbpp/prepro45.out +++ b/tests/functional/zxbpp/prepro45.out @@ -1,4 +1,4 @@ -#line 1 "prepro45.bi" - - -#line 7 "prepro45.bi" +#line 1 "prepro45.bi" + + +#line 7 "prepro45.bi" diff --git a/tests/functional/zxbpp/prepro46.out b/tests/functional/zxbpp/prepro46.out index 0cecdef91..1ba95a53b 100644 --- a/tests/functional/zxbpp/prepro46.out +++ b/tests/functional/zxbpp/prepro46.out @@ -1,6 +1,6 @@ -#line 1 "prepro46.bi" - - - -print 1 -#line 6 "prepro46.bi" +#line 1 "prepro46.bi" + + + +print 1 +#line 6 "prepro46.bi" diff --git a/tests/functional/zxbpp/prepro47.out b/tests/functional/zxbpp/prepro47.out index 3a4b99af7..813aaa1e9 100644 --- a/tests/functional/zxbpp/prepro47.out +++ b/tests/functional/zxbpp/prepro47.out @@ -1,5 +1,5 @@ -#line 1 "prepro47.bi" - -#line 3 "prepro47.bi" - -#line 8 "prepro47.bi" +#line 1 "prepro47.bi" + +#line 3 "prepro47.bi" + +#line 8 "prepro47.bi" diff --git a/tests/functional/zxbpp/prepro48.out b/tests/functional/zxbpp/prepro48.out index 976bbb3f7..bfc8f8d82 100644 --- a/tests/functional/zxbpp/prepro48.out +++ b/tests/functional/zxbpp/prepro48.out @@ -1,5 +1,5 @@ -#line 1 "prepro48.bi" - -#line 3 "prepro48.bi" - -#line 8 "prepro48.bi" +#line 1 "prepro48.bi" + +#line 3 "prepro48.bi" + +#line 8 "prepro48.bi" diff --git a/tests/functional/zxbpp/prepro49.out b/tests/functional/zxbpp/prepro49.out index 7b358facf..87aa9583f 100644 --- a/tests/functional/zxbpp/prepro49.out +++ b/tests/functional/zxbpp/prepro49.out @@ -1,5 +1,5 @@ -#line 1 "prepro49.bi" - -#line 3 "prepro49.bi" - -#line 8 "prepro49.bi" +#line 1 "prepro49.bi" + +#line 3 "prepro49.bi" + +#line 8 "prepro49.bi" diff --git a/tests/functional/zxbpp/prepro50.out b/tests/functional/zxbpp/prepro50.out index e4560671c..76e000824 100644 --- a/tests/functional/zxbpp/prepro50.out +++ b/tests/functional/zxbpp/prepro50.out @@ -1,8 +1,8 @@ -#line 1 "prepro50.bi" - -#line 3 "prepro50.bi" - - -print 1 - -#line 8 "prepro50.bi" +#line 1 "prepro50.bi" + +#line 3 "prepro50.bi" + + +print 1 + +#line 8 "prepro50.bi" diff --git a/tests/functional/zxbpp/prepro51.out b/tests/functional/zxbpp/prepro51.out index 67281624b..f09c1f080 100644 --- a/tests/functional/zxbpp/prepro51.out +++ b/tests/functional/zxbpp/prepro51.out @@ -1,4 +1,4 @@ -#line 1 "prepro51.bi" - - -A(1) +#line 1 "prepro51.bi" + + +A(1) diff --git a/tests/functional/zxbpp/prepro52.out b/tests/functional/zxbpp/prepro52.out index e2c023bc4..b22576dca 100644 --- a/tests/functional/zxbpp/prepro52.out +++ b/tests/functional/zxbpp/prepro52.out @@ -1,5 +1,5 @@ -#line 1 "prepro52.bi" - -#line 3 "prepro52.bi" - -1 +#line 1 "prepro52.bi" + +#line 3 "prepro52.bi" + +1 diff --git a/tests/functional/zxbpp/prepro53.out b/tests/functional/zxbpp/prepro53.out index bacaf8d97..051b7c171 100644 --- a/tests/functional/zxbpp/prepro53.out +++ b/tests/functional/zxbpp/prepro53.out @@ -1,5 +1,5 @@ -#line 1 "prepro53.bi" - -#line 3 "prepro53.bi" - -1 +#line 1 "prepro53.bi" + +#line 3 "prepro53.bi" + +1 diff --git a/tests/functional/zxbpp/prepro54.out b/tests/functional/zxbpp/prepro54.out index dc8ee0c6d..5796d5e88 100644 --- a/tests/functional/zxbpp/prepro54.out +++ b/tests/functional/zxbpp/prepro54.out @@ -1,5 +1,5 @@ -#line 1 "prepro54.bi" - -#line 3 "prepro54.bi" - -#line 7 "prepro54.bi" +#line 1 "prepro54.bi" + +#line 3 "prepro54.bi" + +#line 7 "prepro54.bi" diff --git a/tests/functional/zxbpp/prepro55.out b/tests/functional/zxbpp/prepro55.out index 57bc0cf34..627797956 100644 --- a/tests/functional/zxbpp/prepro55.out +++ b/tests/functional/zxbpp/prepro55.out @@ -1,7 +1,7 @@ -#line 1 "prepro55.bi" - -#line 3 "prepro55.bi" - - -ok -#line 7 "prepro55.bi" +#line 1 "prepro55.bi" + +#line 3 "prepro55.bi" + + +ok +#line 7 "prepro55.bi" diff --git a/tests/functional/zxbpp/prepro56.out b/tests/functional/zxbpp/prepro56.out index 814a6b692..b829f00c2 100644 --- a/tests/functional/zxbpp/prepro56.out +++ b/tests/functional/zxbpp/prepro56.out @@ -1,5 +1,5 @@ -#line 1 "prepro56.bi" - -#line 3 "prepro56.bi" - -#line 7 "prepro56.bi" +#line 1 "prepro56.bi" + +#line 3 "prepro56.bi" + +#line 7 "prepro56.bi" diff --git a/tests/functional/zxbpp/prepro57.out b/tests/functional/zxbpp/prepro57.out index a6d0ba11a..d62e8580b 100644 --- a/tests/functional/zxbpp/prepro57.out +++ b/tests/functional/zxbpp/prepro57.out @@ -1,5 +1,5 @@ -#line 1 "prepro57.bi" - - -PRINT "LANG = es" -#line 5 "prepro57.bi" +#line 1 "prepro57.bi" + + +PRINT "LANG = es" +#line 5 "prepro57.bi" diff --git a/tests/functional/zxbpp/prepro58.out b/tests/functional/zxbpp/prepro58.out index 0d224895e..3ae83cfa0 100644 --- a/tests/functional/zxbpp/prepro58.out +++ b/tests/functional/zxbpp/prepro58.out @@ -1,6 +1,6 @@ -#line 1 "prepro58.bi" -sub test() - asm - ex af,af' - end asm -end sub +#line 1 "prepro58.bi" +sub test() + asm + ex af,af' + end asm +end sub diff --git a/tests/functional/zxbpp/prepro59.out b/tests/functional/zxbpp/prepro59.out index 0fb6b4e16..65a8a93a7 100644 --- a/tests/functional/zxbpp/prepro59.out +++ b/tests/functional/zxbpp/prepro59.out @@ -1,5 +1,5 @@ -#line 1 "prepro59.bi" - -#line 3 "prepro59.bi" - -LET a$ = (a$ + CHR$(0)) +#line 1 "prepro59.bi" + +#line 3 "prepro59.bi" + +LET a$ = (a$ + CHR$(0)) diff --git a/tests/functional/zxbpp/prepro60.out b/tests/functional/zxbpp/prepro60.out index 1835bd294..eae521eb5 100644 --- a/tests/functional/zxbpp/prepro60.out +++ b/tests/functional/zxbpp/prepro60.out @@ -1,6 +1,6 @@ -#line 1 "prepro60.bi" -Function FASTCALL ESXDOSWrite(ByVal handle as Byte, _ - ByVal buffer as UInteger, _ - ByVal nbytes as UInteger) as Uinteger -#line 4 -End Function +#line 1 "prepro60.bi" +Function FASTCALL ESXDOSWrite(ByVal handle as Byte, _ + ByVal buffer as UInteger, _ + ByVal nbytes as UInteger) as Uinteger +#line 4 +End Function diff --git a/tests/functional/zxbpp/prepro61.out b/tests/functional/zxbpp/prepro61.out index 8c6ed5188..c057071d0 100644 --- a/tests/functional/zxbpp/prepro61.out +++ b/tests/functional/zxbpp/prepro61.out @@ -1,6 +1,6 @@ -#line 1 "prepro61.bi" - - -#line 4 "prepro61.bi" - -xx 1 aa 1 x(1) +#line 1 "prepro61.bi" + + +#line 4 "prepro61.bi" + +xx 1 aa 1 x(1) diff --git a/tests/functional/zxbpp/prepro62.out b/tests/functional/zxbpp/prepro62.out index f424ae130..39bb10aaf 100644 --- a/tests/functional/zxbpp/prepro62.out +++ b/tests/functional/zxbpp/prepro62.out @@ -1,7 +1,7 @@ -#line 1 "prepro62.bi" - -#line 3 "prepro62.bi" -#line 4 "prepro62.bi" - -(1 + 1, 2 + 2) -(3 + 1, 4 + 2) +#line 1 "prepro62.bi" + +#line 3 "prepro62.bi" +#line 4 "prepro62.bi" + +(1 + 1, 2 + 2) +(3 + 1, 4 + 2) diff --git a/tests/functional/zxbpp/prepro63.out b/tests/functional/zxbpp/prepro63.out index 51b9f7222..8d9d72c5a 100644 --- a/tests/functional/zxbpp/prepro63.out +++ b/tests/functional/zxbpp/prepro63.out @@ -1,6 +1,6 @@ -#line 1 "prepro63.bi" - - -#line 4 "prepro63.bi" - -XX5 +#line 1 "prepro63.bi" + + +#line 4 "prepro63.bi" + +XX5 diff --git a/tests/functional/zxbpp/prepro64.out b/tests/functional/zxbpp/prepro64.out index 15a3d3114..835aabb03 100644 --- a/tests/functional/zxbpp/prepro64.out +++ b/tests/functional/zxbpp/prepro64.out @@ -1,50 +1,50 @@ -#line 1 "prepro64.bi" - -#line 3 "prepro64.bi" - -#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" - - - - - - - - - - - - - -#line 15 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" - -#pragma push(case_insensitive) -#pragma case_insensitive = true - - - - - - - - - -function FASTCALL pos as ubyte - asm - push namespace core - PROC - - call __LOAD_S_POSN - ld , e - - ENDP - pop namespace - end asm -end function - -#pragma pop(case_insensitive) -#require "sposn.asm" - - -#line 45 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" -#line 5 "prepro64.bi" +#line 1 "prepro64.bi" + +#line 3 "prepro64.bi" + +#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" + + + + + + + + + + + + + +#line 15 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" + +#pragma push(case_insensitive) +#pragma case_insensitive = true + + + + + + + + + +function FASTCALL pos as ubyte + asm + push namespace core + PROC + + call __LOAD_S_POSN + ld , e + + ENDP + pop namespace + end asm +end function + +#pragma pop(case_insensitive) +#require "sposn.asm" + + +#line 45 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" +#line 5 "prepro64.bi" diff --git a/tests/functional/zxbpp/prepro70.out b/tests/functional/zxbpp/prepro70.out index 47bdb70b4..dd55790ed 100644 --- a/tests/functional/zxbpp/prepro70.out +++ b/tests/functional/zxbpp/prepro70.out @@ -1,13 +1,13 @@ -#line 1 "prepro70.bi" -DIM b - - - -#line 1 "prepro70.bi" -DIM b -#line 10 "prepro70.bi" -#line 6 "prepro70.bi" - -DIM a - -#line 10 "prepro70.bi" +#line 1 "prepro70.bi" +DIM b + + + +#line 1 "prepro70.bi" +DIM b +#line 10 "prepro70.bi" +#line 6 "prepro70.bi" + +DIM a + +#line 10 "prepro70.bi" diff --git a/tests/functional/zxbpp/prepro71.out b/tests/functional/zxbpp/prepro71.out index 9d1274edd..98e08c6fb 100644 --- a/tests/functional/zxbpp/prepro71.out +++ b/tests/functional/zxbpp/prepro71.out @@ -1,243 +1,243 @@ -#line 1 "prepro71.bi" -#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/alloc.bas" - - - - - - - - - - - -#line 13 "/zxbasic/src/lib/arch/zx48k/stdlib/alloc.bas" - -#pragma push(case_insensitive) -#pragma case_insensitive = True - - - - - - - - - - - - - - - -function FASTCALL allocate(byval n as uinteger) as uinteger - - - - - asm - push namespace core - ld b, h - ld c, l - jp __MEM_ALLOC - pop namespace - end asm -end function - - - - - - - - - - - - - - - - - -function FASTCALL callocate(byval n as uinteger) as uinteger - - - - - asm - push namespace core - ld b, h - ld c, l - jp __MEM_CALLOC - pop namespace - end asm -end function - - - - - - - - -sub FASTCALL deallocate(byval addr as integer) - - - - asm - push namespace core - jp __MEM_FREE - pop namespace - end asm -end sub - - - - - - - - - - - - - - - - -function FASTCALL reallocate(byval addr as uinteger, byval n as uinteger) as uinteger - - - - - - asm - push namespace core - ex de, hl - pop hl - ex (sp), hl - ld b, h - ld c, l - ex de, hl - jp __REALLOC - pop namespace - end asm -end function - - - - - - - - -function FASTCALL memavail as uInteger - asm - push namespace core - PROC - - LOCAL LOOP - - ld hl, ZXBASIC_MEM_HEAP - ld de, 0 - -LOOP: - - ld c, (hl) - inc hl - ld b, (hl) - inc hl - - - ld a, (hl) - inc hl - ld h, (hl) - ld l, a - - - ex de, hl - add hl, bc - ex de, hl - - - ld a, h - or l - jr nz, LOOP - - ex de, hl - - ENDP - pop namespace - end asm -end function - - - - - - - -function FASTCALL maxavail as uInteger - asm - push namespace core - PROC - - LOCAL LOOP, CONT - - ld hl, ZXBASIC_MEM_HEAP - ld de, 0 - -LOOP: - - ld c, (hl) - inc hl - ld b, (hl) - inc hl - - - ld a, (hl) - inc hl - ld h, (hl) - ld l, a - - - - ex de, hl - or a - sbc hl, bc - add hl, bc - ex de, hl - - - jr nc, CONT - - ld d, b - ld e, c - -CONT: - - ld a, h - or l - jr nz, LOOP - - ex de, hl - - ENDP - pop namespace - end asm -end function - - -#pragma pop(case_insensitive) - -#require "mem/alloc.asm" -#require "mem/free.asm" -#require "mem/realloc.asm" -#require "mem/calloc.asm" - -#line 239 "/zxbasic/src/lib/arch/zx48k/stdlib/alloc.bas" -#line 2 "prepro71.bi" - -PRINT "HOLA" +#line 1 "prepro71.bi" +#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/alloc.bas" + + + + + + + + + + + +#line 13 "/zxbasic/src/lib/arch/zx48k/stdlib/alloc.bas" + +#pragma push(case_insensitive) +#pragma case_insensitive = True + + + + + + + + + + + + + + + +function FASTCALL allocate(byval n as uinteger) as uinteger + + + + + asm + push namespace core + ld b, h + ld c, l + jp __MEM_ALLOC + pop namespace + end asm +end function + + + + + + + + + + + + + + + + + +function FASTCALL callocate(byval n as uinteger) as uinteger + + + + + asm + push namespace core + ld b, h + ld c, l + jp __MEM_CALLOC + pop namespace + end asm +end function + + + + + + + + +sub FASTCALL deallocate(byval addr as integer) + + + + asm + push namespace core + jp __MEM_FREE + pop namespace + end asm +end sub + + + + + + + + + + + + + + + + +function FASTCALL reallocate(byval addr as uinteger, byval n as uinteger) as uinteger + + + + + + asm + push namespace core + ex de, hl + pop hl + ex (sp), hl + ld b, h + ld c, l + ex de, hl + jp __REALLOC + pop namespace + end asm +end function + + + + + + + + +function FASTCALL memavail as uInteger + asm + push namespace core + PROC + + LOCAL LOOP + + ld hl, ZXBASIC_MEM_HEAP + ld de, 0 + +LOOP: + + ld c, (hl) + inc hl + ld b, (hl) + inc hl + + + ld a, (hl) + inc hl + ld h, (hl) + ld l, a + + + ex de, hl + add hl, bc + ex de, hl + + + ld a, h + or l + jr nz, LOOP + + ex de, hl + + ENDP + pop namespace + end asm +end function + + + + + + + +function FASTCALL maxavail as uInteger + asm + push namespace core + PROC + + LOCAL LOOP, CONT + + ld hl, ZXBASIC_MEM_HEAP + ld de, 0 + +LOOP: + + ld c, (hl) + inc hl + ld b, (hl) + inc hl + + + ld a, (hl) + inc hl + ld h, (hl) + ld l, a + + + + ex de, hl + or a + sbc hl, bc + add hl, bc + ex de, hl + + + jr nc, CONT + + ld d, b + ld e, c + +CONT: + + ld a, h + or l + jr nz, LOOP + + ex de, hl + + ENDP + pop namespace + end asm +end function + + +#pragma pop(case_insensitive) + +#require "mem/alloc.asm" +#require "mem/free.asm" +#require "mem/realloc.asm" +#require "mem/calloc.asm" + +#line 239 "/zxbasic/src/lib/arch/zx48k/stdlib/alloc.bas" +#line 2 "prepro71.bi" + +PRINT "HOLA" diff --git a/tests/functional/zxbpp/prepro72.out b/tests/functional/zxbpp/prepro72.out index b21476152..6da2cd04e 100644 --- a/tests/functional/zxbpp/prepro72.out +++ b/tests/functional/zxbpp/prepro72.out @@ -1,13 +1,13 @@ -#line 1 "prepro72.bi" -DIM b - - - -#line 1 "prepro70.bi" -DIM b -#line 10 "prepro70.bi" -#line 6 "prepro72.bi" - -DIM a - -#line 10 "prepro72.bi" +#line 1 "prepro72.bi" +DIM b + + + +#line 1 "prepro70.bi" +DIM b +#line 10 "prepro70.bi" +#line 6 "prepro72.bi" + +DIM a + +#line 10 "prepro72.bi" diff --git a/tests/functional/zxbpp/prepro73.out b/tests/functional/zxbpp/prepro73.out index e3de2597c..75a2c16aa 100644 --- a/tests/functional/zxbpp/prepro73.out +++ b/tests/functional/zxbpp/prepro73.out @@ -1,67 +1,67 @@ -#line 1 "prepro73.bi" -#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" - - - - - - - - - - - - - -#line 15 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" - -#pragma push(case_insensitive) -#pragma case_insensitive = true - - - - - - - - - -function FASTCALL pos as ubyte - asm - push namespace core - PROC - - call __LOAD_S_POSN - ld a, e - - ENDP - pop namespace - end asm -end function - -#pragma pop(case_insensitive) -#require "sposn.asm" - - -#line 45 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" -#line 2 "prepro73.bi" - - - -#line 1 "prepro73.bi" -#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" - - - - - - - - - -#line 45 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" -#line 2 "prepro73.bi" - -#line 7 "prepro73.bi" -#line 6 "prepro73.bi" -#line 7 "prepro73.bi" +#line 1 "prepro73.bi" +#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" + + + + + + + + + + + + + +#line 15 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" + +#pragma push(case_insensitive) +#pragma case_insensitive = true + + + + + + + + + +function FASTCALL pos as ubyte + asm + push namespace core + PROC + + call __LOAD_S_POSN + ld a, e + + ENDP + pop namespace + end asm +end function + +#pragma pop(case_insensitive) +#require "sposn.asm" + + +#line 45 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" +#line 2 "prepro73.bi" + + + +#line 1 "prepro73.bi" +#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" + + + + + + + + + +#line 45 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" +#line 2 "prepro73.bi" + +#line 7 "prepro73.bi" +#line 6 "prepro73.bi" +#line 7 "prepro73.bi" diff --git a/tests/functional/zxbpp/prepro74.out b/tests/functional/zxbpp/prepro74.out index 72e9bfc05..f1b5a1fd5 100644 --- a/tests/functional/zxbpp/prepro74.out +++ b/tests/functional/zxbpp/prepro74.out @@ -1,60 +1,60 @@ -#line 1 "prepro74.bi" -#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" - - - - - - - - - - - - - -#line 15 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" - -#pragma push(case_insensitive) -#pragma case_insensitive = true - - - - - - - - - -function FASTCALL pos as ubyte - asm - push namespace core - PROC - - call __LOAD_S_POSN - ld a, e - - ENDP - pop namespace - end asm -end function - -#pragma pop(case_insensitive) -#require "sposn.asm" - - -#line 45 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" -#line 2 "prepro74.bi" - - - -#line 1 "prepro74.bi" - - -#line 7 "prepro74.bi" - -glibberish -#line 6 "prepro74.bi" -#line 7 "prepro74.bi" - -glibberish +#line 1 "prepro74.bi" +#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" + + + + + + + + + + + + + +#line 15 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" + +#pragma push(case_insensitive) +#pragma case_insensitive = true + + + + + + + + + +function FASTCALL pos as ubyte + asm + push namespace core + PROC + + call __LOAD_S_POSN + ld a, e + + ENDP + pop namespace + end asm +end function + +#pragma pop(case_insensitive) +#require "sposn.asm" + + +#line 45 "/zxbasic/src/lib/arch/zx48k/stdlib/pos.bas" +#line 2 "prepro74.bi" + + + +#line 1 "prepro74.bi" + + +#line 7 "prepro74.bi" + +glibberish +#line 6 "prepro74.bi" +#line 7 "prepro74.bi" + +glibberish diff --git a/tests/functional/zxbpp/prepro75.out b/tests/functional/zxbpp/prepro75.out index 3d2e89be8..60d28b684 100644 --- a/tests/functional/zxbpp/prepro75.out +++ b/tests/functional/zxbpp/prepro75.out @@ -1,9 +1,9 @@ -#line 1 "prepro75.bi" -A = 0 - - -#line 1 "prepro75.bi" -A = 0 -#line 6 "prepro75.bi" -#line 5 "prepro75.bi" -#line 6 "prepro75.bi" +#line 1 "prepro75.bi" +A = 0 + + +#line 1 "prepro75.bi" +A = 0 +#line 6 "prepro75.bi" +#line 5 "prepro75.bi" +#line 6 "prepro75.bi" diff --git a/tests/functional/zxbpp/prepro77.out b/tests/functional/zxbpp/prepro77.out index 05c22b9ac..302836d38 100644 --- a/tests/functional/zxbpp/prepro77.out +++ b/tests/functional/zxbpp/prepro77.out @@ -1,2 +1,2 @@ -#line 1 "prepro77.bi" - +#line 1 "prepro77.bi" + diff --git a/tests/functional/zxbpp/prepro80.out b/tests/functional/zxbpp/prepro80.out index c5f085d99..a543816d3 100644 --- a/tests/functional/zxbpp/prepro80.out +++ b/tests/functional/zxbpp/prepro80.out @@ -1,20 +1,20 @@ -#line 1 "prepro80.bi" - - - - - - - asm - halt - halt - end asm -#line 11 - - asm - halt - halt - end asm -#line 12 - -#line 14 "prepro80.bi" +#line 1 "prepro80.bi" + + + + + + + asm + halt + halt + end asm +#line 11 + + asm + halt + halt + end asm +#line 12 + +#line 14 "prepro80.bi" diff --git a/tests/functional/zxbpp/prepro81.out b/tests/functional/zxbpp/prepro81.out index ff19e067d..b53925261 100644 --- a/tests/functional/zxbpp/prepro81.out +++ b/tests/functional/zxbpp/prepro81.out @@ -1,4 +1,4 @@ -#line 1 "prepro81.bi" - - -#line 6 "prepro81.bi" +#line 1 "prepro81.bi" + + +#line 6 "prepro81.bi" diff --git a/tests/functional/zxbpp/prepro82.out b/tests/functional/zxbpp/prepro82.out index 8387751de..2ce50d3fa 100644 --- a/tests/functional/zxbpp/prepro82.out +++ b/tests/functional/zxbpp/prepro82.out @@ -1,7 +1,7 @@ -#line 1 "prepro82.bi" - - - - - PRINT "test" -#line 7 "prepro82.bi" +#line 1 "prepro82.bi" + + + + + PRINT "test" +#line 7 "prepro82.bi" diff --git a/tests/functional/zxbpp/prepro85.out b/tests/functional/zxbpp/prepro85.out index 2847d5075..8207f63be 100644 --- a/tests/functional/zxbpp/prepro85.out +++ b/tests/functional/zxbpp/prepro85.out @@ -1,132 +1,132 @@ -#line 1 "prepro85.bi" - - -#line 1 "prepro00.bi" -#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" - - - - - - - - - - - -#line 13 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" - -#pragma push(case_insensitive) -#pragma case_insensitive = TRUE - - - - - - - - - - - -function attr(byval row as ubyte, byval col as ubyte) as ubyte - asm - push namespace core - - PROC - LOCAL __ATTR_END - - ld e, (ix+7) - ld d, (ix+5) - - - call __IN_SCREEN - jr nc, __ATTR_END - - call __ATTR_ADDR - ld a, (hl) - -__ATTR_END: - ENDP - - pop namespace - end asm - -end function - - - - - - - - - - - - - - -sub setattr(byval row as ubyte, byval col as ubyte, byval value as ubyte) - asm - push namespace core - - PROC - LOCAL __ATTR_END - - ld e, (ix+7) - ld d, (ix+5) - - - call __IN_SCREEN - jr nc, __ATTR_END - - call __ATTR_ADDR - ld a, (ix+9) - ld (hl), a - -__ATTR_END: - ENDP - - pop namespace - end asm - -end sub - - - - - - - - - - - -function fastcall attraddr(byval row as ubyte, byval col as ubyte) as uinteger - asm - push namespace core - - pop hl - ex (sp), hl - ld d, a - ld e, h - jp __ATTR_ADDR - pop namespace - end asm -end function - - - -#pragma pop(case_insensitive) - - -#require "attr.asm" - - -#require "in_screen.asm" - -#line 125 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" -#line 2 "prepro00.bi" -PRINT "HELLO" -#line 4 "prepro85.bi" +#line 1 "prepro85.bi" + + +#line 1 "prepro00.bi" +#line 1 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" + + + + + + + + + + + +#line 13 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" + +#pragma push(case_insensitive) +#pragma case_insensitive = TRUE + + + + + + + + + + + +function attr(byval row as ubyte, byval col as ubyte) as ubyte + asm + push namespace core + + PROC + LOCAL __ATTR_END + + ld e, (ix+7) + ld d, (ix+5) + + + call __IN_SCREEN + jr nc, __ATTR_END + + call __ATTR_ADDR + ld a, (hl) + +__ATTR_END: + ENDP + + pop namespace + end asm + +end function + + + + + + + + + + + + + + +sub setattr(byval row as ubyte, byval col as ubyte, byval value as ubyte) + asm + push namespace core + + PROC + LOCAL __ATTR_END + + ld e, (ix+7) + ld d, (ix+5) + + + call __IN_SCREEN + jr nc, __ATTR_END + + call __ATTR_ADDR + ld a, (ix+9) + ld (hl), a + +__ATTR_END: + ENDP + + pop namespace + end asm + +end sub + + + + + + + + + + + +function fastcall attraddr(byval row as ubyte, byval col as ubyte) as uinteger + asm + push namespace core + + pop hl + ex (sp), hl + ld d, a + ld e, h + jp __ATTR_ADDR + pop namespace + end asm +end function + + + +#pragma pop(case_insensitive) + + +#require "attr.asm" + + +#require "in_screen.asm" + +#line 125 "/zxbasic/src/lib/arch/zx48k/stdlib/attr.bas" +#line 2 "prepro00.bi" +PRINT "HELLO" +#line 4 "prepro85.bi" diff --git a/tests/functional/zxbpp/prepro90.out b/tests/functional/zxbpp/prepro90.out index fa3b6bb6a..5ab94ed12 100644 --- a/tests/functional/zxbpp/prepro90.out +++ b/tests/functional/zxbpp/prepro90.out @@ -1,7 +1,7 @@ -#line 1 "prepro90.bi" - -#line 3 "prepro90.bi" - -#line 7 "prepro90.bi" - OK -#line 9 "prepro90.bi" +#line 1 "prepro90.bi" + +#line 3 "prepro90.bi" + +#line 7 "prepro90.bi" + OK +#line 9 "prepro90.bi" diff --git a/tests/functional/zxbpp/prepro91.out b/tests/functional/zxbpp/prepro91.out index 98e31fcb2..8de0031a7 100644 --- a/tests/functional/zxbpp/prepro91.out +++ b/tests/functional/zxbpp/prepro91.out @@ -1,5 +1,5 @@ -#line 1 "prepro91.bi" - -#line 7 "prepro91.bi" - OK -#line 9 "prepro91.bi" +#line 1 "prepro91.bi" + +#line 7 "prepro91.bi" + OK +#line 9 "prepro91.bi" diff --git a/tests/functional/zxbpp/prepro92.out b/tests/functional/zxbpp/prepro92.out index bcf57a7a8..ddb01a2ed 100644 --- a/tests/functional/zxbpp/prepro92.out +++ b/tests/functional/zxbpp/prepro92.out @@ -1,7 +1,7 @@ -#line 1 "prepro92.bi" - - - -#line 7 "prepro92.bi" -OK -#line 9 "prepro92.bi" +#line 1 "prepro92.bi" + + + +#line 7 "prepro92.bi" +OK +#line 9 "prepro92.bi" diff --git a/tests/functional/zxbpp/prepro93.out b/tests/functional/zxbpp/prepro93.out index 5be344977..b00ab178a 100644 --- a/tests/functional/zxbpp/prepro93.out +++ b/tests/functional/zxbpp/prepro93.out @@ -1,7 +1,7 @@ -#line 1 "prepro93.bi" - - - -#line 9 "prepro93.bi" -OK -#line 11 "prepro93.bi" +#line 1 "prepro93.bi" + + + +#line 9 "prepro93.bi" +OK +#line 11 "prepro93.bi" diff --git a/tests/functional/zxbpp/prepro94.out b/tests/functional/zxbpp/prepro94.out index f712fd64e..9a631f91b 100644 --- a/tests/functional/zxbpp/prepro94.out +++ b/tests/functional/zxbpp/prepro94.out @@ -1,6 +1,6 @@ -#line 1 "prepro94.bi" - - -#line 8 "prepro94.bi" - OK -#line 10 "prepro94.bi" +#line 1 "prepro94.bi" + + +#line 8 "prepro94.bi" + OK +#line 10 "prepro94.bi" diff --git a/tests/functional/zxbpp/prepro95.out b/tests/functional/zxbpp/prepro95.out index 233206023..5a7dd76ff 100644 --- a/tests/functional/zxbpp/prepro95.out +++ b/tests/functional/zxbpp/prepro95.out @@ -1,5 +1,5 @@ -#line 1 "prepro95.bi" - -#line 11 "prepro95.bi" - OK -#line 13 "prepro95.bi" +#line 1 "prepro95.bi" + +#line 11 "prepro95.bi" + OK +#line 13 "prepro95.bi" diff --git a/tests/functional/zxbpp/spectrum.out b/tests/functional/zxbpp/spectrum.out index 81886253d..752c23498 100644 --- a/tests/functional/zxbpp/spectrum.out +++ b/tests/functional/zxbpp/spectrum.out @@ -1,5 +1,5 @@ -#line 1 "spectrum.bi" - - - -PRINT "HELLO WORLD!" +#line 1 "spectrum.bi" + + + +PRINT "HELLO WORLD!" diff --git a/tests/functional/zxbpp/stringizing0.out b/tests/functional/zxbpp/stringizing0.out index dda07c45b..a8d4304b7 100644 --- a/tests/functional/zxbpp/stringizing0.out +++ b/tests/functional/zxbpp/stringizing0.out @@ -1,6 +1,6 @@ -#line 1 "stringizing0.bi" - - -#line 4 "stringizing0.bi" - -"1" +#line 1 "stringizing0.bi" + + +#line 4 "stringizing0.bi" + +"1" diff --git a/tests/functional/zxbpp/stringizing1.out b/tests/functional/zxbpp/stringizing1.out index 17de860a1..41500b96a 100644 --- a/tests/functional/zxbpp/stringizing1.out +++ b/tests/functional/zxbpp/stringizing1.out @@ -1,8 +1,8 @@ -#line 1 "stringizing1.bi" - - -#line 4 "stringizing1.bi" -#line 5 "stringizing1.bi" - -"ab" -"5" +#line 1 "stringizing1.bi" + + +#line 4 "stringizing1.bi" +#line 5 "stringizing1.bi" + +"ab" +"5" diff --git a/tests/functional/zxbpp/stringizing2.out b/tests/functional/zxbpp/stringizing2.out index 84d7d3d01..d6689b993 100644 --- a/tests/functional/zxbpp/stringizing2.out +++ b/tests/functional/zxbpp/stringizing2.out @@ -1,6 +1,6 @@ -#line 1 "stringizing2.bi" - - -#line 4 "stringizing2.bi" - -"""a """" string""" +#line 1 "stringizing2.bi" + + +#line 4 "stringizing2.bi" + +"""a """" string""" diff --git a/tests/functional/zxbpp/token-paste0.out b/tests/functional/zxbpp/token-paste0.out index b623d4d94..366812a66 100644 --- a/tests/functional/zxbpp/token-paste0.out +++ b/tests/functional/zxbpp/token-paste0.out @@ -1,9 +1,9 @@ -#line 1 "token-paste0.bi" - - -#line 4 "token-paste0.bi" - -AB - - -A ## B +#line 1 "token-paste0.bi" + + +#line 4 "token-paste0.bi" + +AB + + +A ## B diff --git a/tests/functional/zxbpp/token-paste1.out b/tests/functional/zxbpp/token-paste1.out index 9ecbbc591..cce8bdbd6 100644 --- a/tests/functional/zxbpp/token-paste1.out +++ b/tests/functional/zxbpp/token-paste1.out @@ -1,8 +1,8 @@ -#line 1 "token-paste1.bi" - - -#line 4 "token-paste1.bi" - -#line 6 "token-paste1.bi" - -(AA 1) +#line 1 "token-paste1.bi" + + +#line 4 "token-paste1.bi" + +#line 6 "token-paste1.bi" + +(AA 1) diff --git a/tests/runtime/Makefile b/tests/runtime/Makefile index 4808da7a1..163511909 100644 --- a/tests/runtime/Makefile +++ b/tests/runtime/Makefile @@ -1,7 +1,7 @@ -# vim:ts=4:noet: - -.PHONY: test - -test: - ./test_all - +# vim:ts=4:noet: + +.PHONY: test + +test: + ./test_all + diff --git a/tests/runtime/run b/tests/runtime/run index 4ea370eef..84cf8be6b 100755 --- a/tests/runtime/run +++ b/tests/runtime/run @@ -1,9 +1,9 @@ -# vim:et:ts=4: - -RUN=$(basename -s .bas $1) -EXT=tzx -rm -f "$RUN.$EXT" -killall fuse 2>/dev/null -../../zxbc.py --$EXT -aB "$@" --debug-memory || exit 1 -#fuse --auto-load --speed=100 --machine=plus2 "$RUN.$EXT" & -speccy "$RUN.$EXT" +# vim:et:ts=4: + +RUN=$(basename -s .bas $1) +EXT=tzx +rm -f "$RUN.$EXT" +killall fuse 2>/dev/null +../../zxbc.py --$EXT -aB "$@" --debug-memory || exit 1 +#fuse --auto-load --speed=100 --machine=plus2 "$RUN.$EXT" & +speccy "$RUN.$EXT" diff --git a/tests/runtime/test_all b/tests/runtime/test_all index 5b2cb3c9f..c43884b2e 100755 --- a/tests/runtime/test_all +++ b/tests/runtime/test_all @@ -1,4 +1,4 @@ -#!/bin/bash - -# run tests in parallel, one per CPU -parallel ./test_case ::: cases/*.bas +#!/bin/bash + +# run tests in parallel, one per CPU +parallel ./test_case ::: cases/*.bas diff --git a/tests/runtime/test_case b/tests/runtime/test_case index 5ca2603f9..792c0fa13 100755 --- a/tests/runtime/test_case +++ b/tests/runtime/test_case @@ -1,17 +1,17 @@ -#!/bin/bash -# vim:et:ts=4: - -# Test a single case (prog.bas file) -# A RAM dump /expected/prog.tzx.scr must exists - -TIMEOUT=180 -TIMEKILL=$((TIMEOUT+30)) - -echo -n "Testing $(basename $1): " -RUN=$(basename -s .bas $1).z80 -EXPECTED=$(basename -s .bas $1).tzx.scr -../../zxbc.py -f z80 -aB -o "$RUN" $1 $(grep "PARAMS:" $1 |cut -d':' -f2-) --debug-memory 2>/dev/null -timeout -k $TIMEKILL $TIMEOUT ./check_test.py "$RUN" "./expected/${EXPECTED}" -RETVAL=$? -rm -f "$RUN" 2>/dev/null -exit $RETVAL +#!/bin/bash +# vim:et:ts=4: + +# Test a single case (prog.bas file) +# A RAM dump /expected/prog.tzx.scr must exists + +TIMEOUT=180 +TIMEKILL=$((TIMEOUT+30)) + +echo -n "Testing $(basename $1): " +RUN=$(basename -s .bas $1).z80 +EXPECTED=$(basename -s .bas $1).tzx.scr +../../zxbc.py -f z80 -aB -o "$RUN" $1 $(grep "PARAMS:" $1 |cut -d':' -f2-) --debug-memory 2>/dev/null +timeout -k $TIMEKILL $TIMEOUT ./check_test.py "$RUN" "./expected/${EXPECTED}" +RETVAL=$? +rm -f "$RUN" 2>/dev/null +exit $RETVAL diff --git a/tests/runtime/update_test.sh b/tests/runtime/update_test.sh index 929195ac6..8f0c116d2 100755 --- a/tests/runtime/update_test.sh +++ b/tests/runtime/update_test.sh @@ -1,7 +1,7 @@ -#!/bin/bash - -# ./run $1 -NAME=$(basename -s .bas $1).z80 -../../zxbc.py -f z80 -aB "$@" --debug-memory || exit 1 -./update_test.py $NAME -mv $NAME.scr expected/$(basename -s .bas $1).tzx.scr +#!/bin/bash + +# ./run $1 +NAME=$(basename -s .bas $1).z80 +../../zxbc.py -f z80 -aB "$@" --debug-memory || exit 1 +./update_test.py $NAME +mv $NAME.scr expected/$(basename -s .bas $1).tzx.scr diff --git a/tools/profile.sh b/tools/profile.sh index e0baf1687..3ffdc6530 100755 --- a/tools/profile.sh +++ b/tools/profile.sh @@ -1,4 +1,4 @@ -#!/bin/bash - -python -m cProfile "$@" - +#!/bin/bash + +python -m cProfile "$@" +