juegos

Juegos de terminal de Pancho: Catan (TUI, GUI, web, servidor, PicoCalc), ajedrez, calculadora y minijuegos
git clone https://git.lu3dhn.xyz/juegos.git
Log | Files | Refs

CLAUDE.md (24933B)


      1 # CLAUDE.md
      2 
      3 This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
      4 
      5 Juegos de terminal de Pancho. Repo git (desde v1.0, 2026-10-03): `origin` = CT111
      6 (`ssh://git@relay.lu3dhn.xyz:2222/srv/git/juegos.git`), `publico` = espejo de solo lectura en el
      7 VPS (`git clone https://catan.lu3dhn.xyz/git/juegos.git`). Versiones con tags semver (`v1.0`,
      8 `v1.1` algo nuevo, `v1.0.1` arreglos); el menú de Catan muestra `git describe`
      9 (`catan/linux/version.h`, generado por el Makefile). Publicar Catan: commitear y tagear,
     10 `make -C catan release` (catan.exe + catan-linux compilado en docker ubuntu:22.04 con
     11 `tools/linux-release.sh`: imagen `catan-release:ubuntu22.04` con SDL3 ya compilado, se arma una vez;
     12 por defecto en el CT112 por ssh, `RELEASE_HOST=local` usa el docker de la X1 con `sudo -A`/`sudo-askpass`), `make publish`
     13 (push a los dos remotos + binarios a `https://catan.lu3dhn.xyz/descargas/`), `make deploy-web`
     14 y el servidor al VPS. Solo una sesión maneja git y producción; las demás no commitean ni tocan
     15 el VPS.
     16 
     17 - `comun/` — biblioteca compartida por los juegos en C (gfx, menús, TUI/SDL, red, servidor de partidas, HAL de la PicoCalc). Ver `comun/README.md`.
     18 - `catan/` — Catan en C99 portable: TUI, GUI SDL3, servidor para VPS y firmware PicoCalc. Usa `comun/`.
     19 - `ajedrez/` — ajedrez completo en Bash (didáctico). No se toca.
     20 - `mini/` — 7 minijuegos para la PicoCalc sobre `comun/` (buscaminas, víbora, bloques, 2048, sudoku, generala, palabra). Ver `mini/README.md` y `mini/CONVENCIONES.md`; `make` simuladores, `make pico` firmware de todos en `mini/sd/`.
     21 - `calc/` — calculadora natural estilo Casio fx-991 (exactos, ecuaciones, gráficos, programador, estadística): terminal, ventana y PicoCalc. Ver `calc/README.md`; `make test`, `make pico`.
     22 - `kit-picocalc/` — plantilla + guías (`CHATGPT.md`, `PICOCALC-MANIAS.md`) para Félix, con una copia de `comun/{base,gfx,pico}`.
     23 - `ajedrez-c/` — port del ajedrez a C sobre `comun/`: TUI, GUI, servidor (7374) y PicoCalc.
     24 
     25 `comun/base/form.h`: `FORM_MAX_FIELDS` (20 por defecto, la Pico tiene poca pila; Catan de PC compila con 32 para el formulario de 6 asientos).
     26 
     27 `comun/` no puede saber nada de ningún juego: cada juego se identifica con su `app_config.h` (APP_NAME, APP_TITLE, archivos de la SD) y se enchufa por hooks (`GameModule` del servidor, `gfx_form_backdrop`, `tk_form_logo`). Si un cambio en `comun/` toca a Catan, verificar Catan (`make test`, `make pico`). La idea es poder publicar `comun/` como repo propio e incluirlo en cada juego con `git subtree`.
     28 
     29 Comentarios y docs en castellano. En Catan los comentarios son normales (no didácticos) y la UI es bilingüe es/en; en ajedrez los comentarios son didácticos a propósito y la UI está en inglés.
     30 
     31 ## Catan (`catan/`)
     32 
     33 ### Comandos
     34 
     35 ```sh
     36 make                 # catan, catan-server, catan-picosim
     37 make test            # ./catan --selftest + --sim 200 4 + --sim 200 2 2p + --sim 100 6 + --sim 100 4 mar + islas/archi cortos + dos --duel cortos
     38 make debug && make test   # recompila con ASan/UBSan
     39 ./catan --sim N jug [2p|mar|islas|archi|-] [mapa 0..4]   # un mapa puntual (para medir si es parejo: victorias por asiento)
     40 ./catan --selftest   # solo reglas/serialización/replay/redacción (linux/selftest.c)
     41 ./catan --sim N [jugadores] [2p]   # N partidas bot vs bot verificando invariantes
     42 ./catan --duel N jugadores iter horizonte [2p|-] [semilla] [c]   # bot ISMCTS vs heurísticos: % de victorias
     43 make catan-duel && ./catan-duel --duel 80 4 100 8 - 7   # búsqueda NUEVA vs la de DUEL_BASE (último tag): así se mide un cambio de bots
     44 make catan-server-static           # servidor estático para el VPS (instalar con comun/deploy/install-vps.sh)
     45 make pico            # firmware PicoCalc → sd/pico1-apps/Catan.uf2 y sd/firmware/Catan.bin
     46 make web             # PWA (emscripten + SDL3 estático) → web/dist/; make deploy-web la sube al VPS (/var/www/catan, Caddy)
     47 ```
     48 
     49 Depuración sin mirar la pantalla (GUI y simulador): `APP_SHOT=f.ppm` vuelca cada cuadro, `APP_KEYS="..."` alimenta teclas (`~` Enter, `<` Esc, `>` Tab, `^ v { }` flechas, `.` pausa, `!` salir); con `SDL_VIDEO_DRIVER=offscreen` no abre ventana. La TUI se captura con tmux (`capture-pane -e -p`) y `comun/tools/ansi2png.py`.
     50 
     51 `./catan-gui` y `./picocalc-sim` son wrappers shell de `./catan --gui` y `./catan-picosim --scale 3`.
     52 
     53 Al correr pruebas o el binario, aislar `XDG_CONFIG_HOME`/`XDG_DATA_HOME` en un directorio temporal: el usuario los tiene seteados y `linux/session.c` guarda ahí config, partidas (`catan/saves/`) y tokens online — un test ya le pisó la config una vez.
     54 
     55 `make pico` necesita `arm-none-eabi-gcc` y `PICO_SDK_PATH` (por defecto `~/pico-sdk`). El firmware no tiene flasheo por USB: se carga desde la SD. Antes de tocar hardware, probar con `./picocalc-sim`, que corre el mismo `pico/app.c`.
     56 
     57 ### Arquitectura
     58 
     59 - **`core/`**: C99 puro, **sin malloc ni stdio ni SO** — compila tal cual para Linux, el servidor y el RP2040. No agregar dependencias de libc más allá de `stdint`/`stdbool`/`string`; hay helpers propios de texto (`str_put`, `str_int`, `str_scan_int`).
     60   - `rng`/`str_*`/`g_lang`/`form` vienen de `comun/base`.
     61   - `catan.h`: `Game`, `Move`, `Opts`, `Topo`. Tableros = familia × `NMAPS` (5) mapas (`BF_CLASSIC`, `BF_LARGE`, `BF_SEA1..3`; tablero = familia*5 + `Opts.map`; `topo_of(board)`, `game_topo(g)`, `board_name`). Todos son dibujos de texto en `LAYOUT` (`core/board.c`: 'L' tierra de arranque, 'I' isla, 'D' desierto fijo, '~' mar, 'P' mar con el pirata); el mapa 0 de cada familia es el de siempre y da los mismos índices y el mismo azar (partidas viejas intactas). Los mapas sin mar de una familia tienen la misma cantidad de hexágonos que el oficial (clásico 19: oficial, lago, rectángulo, media luna, dos lóbulos; grande 30, ídem). `Opts.bseed`: semilla del tablero que elige quien crea mirando la vista previa (`board_make`; los dados siguen saliendo de la semilla de la partida/servidor); cabecera `M <mapa> ` antes de `G`/`S` si el mapa no es 0; OPTS 11º = mapa, 12º = bseed (servidor y guardado local). Cada mapa nuevo se mide con `--sim 800` (victorias por asiento parejas) antes de sumarlo. Clásico (19 hex, 54 vértices, 72 aristas, 9 puertos) y grande de la expansión 5-6 (30/80/109/11). `NHEX`/`NVERT`/`NEDGE`/`NPORT`/`MAXP` son **máximos** (la Pico compila con `CATAN_SMALL`: solo clásico y 4 jugadores); los recorridos van hasta `t->nhex`/`nvert`/`nedge`/`nport`. El tablero lo decide la cantidad de jugadores (`opts_board`: más de 4 → grande), así la cabecera `G` no cambió (np va antes de los arreglos). Banco y mazo por partida (`bank_start`: 19/24, `dev_counts`: 25/34). El clásico usa el azar exactamente como siempre (el servidor regenera el tablero desde la semilla).
     62   - Navegantes (`Opts.scenario` = `SCEN_SEA1` "Nuevas costas", `SCEN_SEA2` "Cuatro islas", `SCEN_SEA3` "Archipiélago"; `opts_scenario`, 3-4 jugadores, 13 puntos), 5 mapas propios cada uno. Regiones = tierras separadas por mar (`Topo.region`); las que tienen 'L' son de arranque (`Topo.home`). Reparto (`board_sea`): tierra de arranque en proporción clásica, islas con oro (1 cada 4) y lo escaso; con varias tierras de arranque se re-sortea hasta que sean parejas (`home_varied`, `home_pips_even`) y los puertos se reparten igual entre ellas. Terrenos `T_SEA`/`T_GOLD` (`ter_land`, `ter_res`). Barcos (`M_SHIP`, `Game.ship[]`, `Player.ships`): por agua, se encadenan con barcos o edificios propios (ruta↔barco solo en un edificio, `edge_connects`); `M_MOVESHIP` uno por turno, barco suelto en una punta y no recién puesto. Pirata (`Game.pirate`): el 7/caballero a un hex de mar mueve el pirata, roba a barcos y bloquea los barcos de alrededor. Oro → `PH_GOLD`/`M_GOLD` (cada uno elige). El primer pueblo en cada región donde no arrancaste da +2 (`Player.bonus`; el setup marca `Player.isles`). El setup va solo en tierras de arranque. `Game.setup_v` es int16 (hay más de 127 vértices). Cabecera `S <escenario> …`; 10º campo de OPTS (servidor y partidas guardadas) = escenario. `--sim N 4 mar|islas|archi [mapa]`, `hotseat --mar|--islas|--archi --mapa N`. Con 3 jugadores en mar el primero gana algo más (~36 %). El bot difícil rinde menos acá (35-40 % vs 25 % de base): su evaluación no valora barcos ni islas.
     63   - 5-6 "jugadores en pareja" (2022): al terminar el turno el asiento +3 juega `PH_PAIRED` (construir, comprar, banco; sin cartas ni trueques), se saltea solo si no le alcanza para nada (`game_pair`, `pair_can_act`, `Game.owner`).
     64   - Modelo autoridad: `auth.c` (`Auth`) valida con `game_check`, resuelve el azar (dados, robos, mazo) y emite la movida resuelta; todos los nodos aplican con `game_apply`/`game_apply_u`. Una partida = cabecera (`hdr_fmt`) + log de movidas, así que guardado, reanudar, reconexión y juego asincrónico son todos replay del log. `move_redact` oculta info privada (cartas de desarrollo) por espectador.
     65   - Undo: `game_apply_u` guarda fotos en `Undo` de forma determinista en todos los nodos; `M_UNDO` solo aplica a acciones sin azar ni otros jugadores.
     66   - `host.c`: autoridad + bots + tipo de asiento; lo usan hotseat, host LAN, el servidor y la PicoCalc "A".
     67   - `ctrl.c`: máquina de estados de interacción **compartida** por TUI, GUI y PicoCalc (teclas abstractas / clicks resueltos → `Move`).
     68   - i18n: `strings.h` es una X-macro `X(id, es, en)`; todo texto visible va ahí y se lee con `tr()`.
     69   - Reglas configurables en `Opts` (variante 2p, ladrón amable, fichas, etc.).
     70   - Bots: tres niveles, todos ISMCTS (`search.c`, `search_decide` + `SearchCfg` en `Host.bot`) con distinta cantidad de iteraciones: fácil 25 (empata con el bot de reglas), medio 300 (le gana ~57 %), difícil 30000 en la PC / 20000 en el servidor (`bot_iters` en `linux/session.c`; config `bot_level`). `bot.c` es el bot de reglas: ya no es un nivel, queda para los playouts, las ausencias y el fallback (iters 0). Determiniza solo lo oculto para ese asiento, candidatas podadas con `bot_int.h`, playouts con `bot_decide`, horizonte 8 + evaluación. Árbol estático (`SEARCH_NODES`, 1024 en la Pico): una búsqueda a la vez. Corta por tiempo: 300 ms Linux (`sess_set_bots`), 150 ms servidor, 1200 ms Pico. **Todo cambio de bots se mide nueva contra vieja**: `make catan-duel` (lleva además la `search.c` de `DUEL_BASE`, por defecto el último tag, en `duel/search_old.c` con el símbolo renombrado a `search_old_decide`) y `./catan-duel --duel 80 4 100 8 - <semilla>` enfrenta la búsqueda nueva contra la vieja en la misma partida rotando asientos (base 25 % con 4 jugadores; correr dos semillas, el ruido con 80 partidas es ±8 puntos). `--duel` en `./catan` sigue siendo contra los heurísticos y solo sirve de sanidad.
     71 - **`gfx/`** (`catan_gfx.h`): tablero y pantallas de Catan en píxeles (layouts compact 320x320 y wide) sobre `comun/gfx`. `geom_fit(geo, game, ...)` encuadra cualquier tablero por los límites de sus vértices. Panel ancho: con más de 4 jugadores las filas son más bajas; acciones y costos van en dos columnas.
     72 - **GUI ancha**: lienzo lógico de 640x360 (16:9) con escala **entera** (`gk_open_fluid` en `comun/linux/guikit.c`: 2x en 720p, 3x en 1080p; un factor no entero deja la letra despareja). Pensada para 720p/1080p (los amigos de Pancho juegan en Windows con `catan.exe`). Fuentes en `comun/gfx/font.c`, generadas por `comun/tools/mkfont.py` desde `comun/tools/fonts/`: Terminus 6x12 (normal, y engrosada a 7x12 como negrita) y Spleen 5x8 (chica, con los acentos del castellano agregados).
     73 - **`linux/`**: cliente. `frontend.h` define la interfaz TUI (`tui.c`, sobre `comun/linux/tuikit`; colores opacos propios — el usuario no quiere depender de la transparencia de su terminal) / GUI (`gui_sdl.c`, sobre `guikit`). `app.c` menús y bucle, `session.c` guardado, `net.c`/`online.c` red (sobre `netline`).
     74 - **Web/PWA** (`make web`, `web/`): el mismo cliente Linux (sin TUI ni LAN, `#ifdef __EMSCRIPTEN__`) compilado con `-sASYNCIFY`: los bucles bloqueantes y el `poll()` de red ceden al navegador solos; los sockets BSD van por un WebSocket cuya URL fija pone `web/shell.html` (`/ws` de la misma página, o `?ws=ws://localhost:7373` para probar). `comun/server/ws.c` hace que `catan-server` acepte WebSocket en el mismo puerto (detecta `GET ` en el primer paquete; `-DSRV_NO_WS` lo saca); Caddy en el VPS hace TLS y proxy de `/ws`. Datos en `/data/catan` sobre IDBFS (`web/pre.js`; `paths_flush()` espera el sync tras guardar). SDL3 en emscripten no muestra el teclado táctil: `gk_text_input` (overlay HTML) via `Frontend.text_input` para chat y campos de formulario. Pantalla táctil (`Frontend.touch`: en la web según `navigator.maxTouchPoints`, así en una PC con navegador no hay barra táctil; `APP_TOUCH=1` en la GUI nativa para probar): un "≡" en la barra de arriba (id `PK_APP` 12 → `ui.modal = 2`: Ayuda/Salir) y un "Volver" (id 7 = Esc) que aparece en `bottom_lines` solo cuando hay algo que cancelar; 8 = sí del diálogo de salir, 9/10 = volver/juego libre al terminar; `modal_menu` lleva una "x" (id 7) salvo los menús obligatorios; `gfx_touch` (global de `comun/gfx`) agranda filas de formularios, menús modales, acciones y +/- de cartas, y la tolerancia de toque del tablero. Arrastrar con el botón apretado (o el dedo) sobre el registro = rueda (`guikit.c`: cada 33 px una muesca, `EV_WHEEL` en el punto donde empezó). Layout **vertical** (`screen_tall`, elegido cuando la superficie es más alta que ancha): tablero arriba del ancho entero, panel apilado abajo. `gk_open_fluid` calcula la escala entera con "lado largo x lado corto" para que sirva en las dos orientaciones.
     75 - **Blanquear contraseñas**: `ADMIN <secreto> RESET <código> <llave> <nombre>` en `srv.c` (el nombre al final, puede tener espacios) (secreto en `<datos>/admin.key`); `comun/deploy/admin.sh` → `catan-admin reset|partidas` en el VPS (`install-vps.sh` lo instala).
     76 - **Online sin cuentas**: el cliente deriva una clave de nombre + contraseña (`seat_key` en `linux/online.c`, SHA-256 de `comun/base/sha256.c`) y la manda con `AUTH`; esa clave es el token del asiento. `JOIN` con el mismo nombre y clave vuelve al asiento; `MINE` lista tus partidas desde cualquier compu. Sin contraseña sigue la lista local de tokens (LAN, partidas viejas). Ausencia: 8º campo de OPTS en minutos; `catan_game.c` lleva `since[]` (reloj de pared, se reinicia al cargar) y el bot (búsqueda) juega por el asiento vencido hasta que deja de tocarle.
     77 - **Chat**: `SAY <para|-1> <texto>` → `CHAT <de> <para> <texto>` (en `comun/server`, persistido en `<partida>/chat`; al entrar se reenvían las últimas 150 que ese asiento puede ver). En el cliente las líneas de chat van al registro con marca `\1` (todos) / `\2` (privado); la caja de chat está siempre visible al pie del panel (click o F2 para escribir; Enter manda, Esc suelta), el chat va del color de quien habla (la marca lleva el asiento), `/w nombre texto` (o /msg, /whisper) es privado (el nombre puede tener espacios: gana el nombre completo más largo). Registro con barra de scroll (click arriba/abajo de la barrita), RePág/AvPág/Fin (también escribiendo) y rueda (`EV_WHEEL` en `comun/base/input.h`).
     78 - **Cursor del tablero**: las flechas caminan por vecinos (vértice→vértice, arista→arista que comparte vértice, hex→hex) aunque no sean válidos (`Ctrl.pos`, `ctrl_cursor_pos`; gris si no se puede); Tab salta al próximo válido.
     79 - **Colores**: `PLAYER_COLOR` es modificable (`PLAYER_COLOR_DEF`: rojo, azul, verde, naranja); "Colores de los jugadores" en el menú guarda `color1..4=#rrggbb` en la config (`settings_apply_colors`). Servidor online por defecto: `APP_SERVER` en `app_config.h` (`lu3dhn.xyz:7373`, el VPS de Oracle `ssh vps`; `catan-server` por systemd con datos en /var/lib/catan; puerto 7373 abierto en iptables y en la Security List de Oracle — se toca con `~/.oci/venv/bin/oci` o su SDK de Python). Actualizar: `make catan-server-static`, copiar a `/usr/local/bin/catan-server`, `systemctl restart catan-server`.
     80 - **Vista previa del mapa**: al darle Empezar/Crear (local, online y LAN) se muestra el tablero (`map_preview` en `linux/app.c`, `UiInfo.preview`; botones `PK_APP` 20-24): R otro reparto (nueva `bseed`), flechas izq/der otro mapa, Enter jugar, Esc vuelve al formulario. El formulario tiene "Mapa" (al azar o 1-5 con nombre, `map_choices`); con el tablero de principiante no hay vista previa.
     81 - **Colocación inicial**: el pueblo y la ruta quedan en borrador en `Ctrl` (`draft_v/draft_e`, se dibujan en el color del jugador) hasta confirmar (`I_SETUP_OK`); `u`/Esc deshacen un paso; al confirmar salen SETTLE y la ROAD encolada (`ctrl_next`, que los bucles de `app.c`/`pico/app.c` vacían). Online, el host elige la colocación (9º campo de OPTS en `catan_game.c`: 0 por turnos, >0 plazo propio en minutos, -1 automática: el bot coloca por todos) y el nivel de los bots (13º campo de OPTS / 11º de CREATE: 1 difíciles, 0 fáciles, 2 medios; falta = difíciles). **Quién empieza se sortea**: `Opts.first` (`OPTS_FIRST_RANDOM` = lo resuelve `host_init` con la semilla, igual en todos los nodos; 14º campo de OPTS del servidor, 13º del guardado local), la cabecera lleva el prefijo `F <asiento>` (como `M`) si no es el 0, y `game_init` da la vuelta a la mesa desde ahí; cabeceras y partidas viejas sin el campo arrancan por el 0.
     82 - **Registro**: completo (crece sin límite en `Session.log`). Marca + asiento al principio de cada línea: `\1` chat, `\2` privado (enteros del color de quien habla), `\3` movida (solo el nombre en color), `\4` algo que te pasó a vos (te robaron: resaltado). Robos: la carta la ven solo ladrón y víctima (`move_public`, `move_describe_for`). Panel: cada jugador muestra las fichas que le quedan (`gfx_piece`).
     83 - **Exportar**: `server/export.c` (compartido): cabecera, tablero, turno por turno (con dónde se construyó y qué produjo cada tirada) y movidas de máquina. El servidor mantiene `<partida>/partida.txt` al día con todo + chat (gancho `export` del `GameModule`); F5 en el cliente exporta lo que viste a `data_dir()/exportes/`.
     84 - **Contraofertas**: `M_COUNTER r=doy r2=pido` de quien responde (`offer.resp = 2`, términos en `offer.cgive/cget`); `M_CONFIRM` cierra con la aceptación o con la contraoferta de ese asiento. Los bots contraofertan (`bot_counter`), ofrecen 1 por 1 una vez por turno (`bot_offer`, `Game.offers`) y cierran con la mejor respuesta (`bot_close_offer`); mientras faltan respuestas `bot_decide` no juega y `host.c` espera `HOST_OFFER_WAIT` pasos (~15 s) antes de cerrar igual.
     85 - **`server/`**: `catan_game.c` es Catan como `GameModule` del servidor de `comun/server` (partidas asincrónicas persistentes). Protocolo general en `comun/server/srv.h`, lo de Catan en `server/server.h`. También se embebe en `catan host` (LAN). El formato en disco es compatible con el servidor anterior a `comun/`.
     86 - **`pico/`**: `app.c` portable sobre `comun/pico/hal.h` (`hal_rp2040.c` hardware, `hal_sim.c` SDL). Cable 1v1 por UART1 (J703, GP4/GP5) con checksum por línea (`comun/pico/link.c`); la PicoCalc A es autoridad.
     87 
     88 ### PicoCalc: restricciones que costaron (implementadas en `comun/pico`)
     89 
     90 - `comun_picocalc_app()` (`comun/pico/comun_pico.cmake`) arma dos targets: `.uf2` normal (uf2loader, `/pico1-apps`) y `_sd200k` enlazado en flash+200k (`memmap_sdboot_200k.ld`) para el "Bootloader v0.5" de ClockworkPi (`/firmware/*.bin`). El `.bin` se parchea con `comun/tools/patch_reset.py` para que el vector de reset apunte a `comun_early_reset` (el cargador salta con IRQs armadas).
     91 - El cargador deja **core 1 corriendo y dibujando** → `multicore_reset_core1()` antes de tocar el LCD.
     92 - LCD ST7365P: cada comando con sus parámetros bajo un mismo CS bajo; la lectura del LCD no funciona.
     93 - **No escribir la flash de la Pico** (`flash_range_erase/program`) sin preguntarle antes al usuario. La persistencia va en la SD.
     94 - `comun/pico/sdlog.c` es un FAT32 mínimo que solo reescribe el contenido de archivos pre-creados en la raíz de la SD (no crea archivos ni toca la FAT): `CATAN.LOG` (diagnóstico vía `hal_log`, sin consola serie) y `CATAN.SAV` (partida: dos slots alternados con cabecera `CSV1` + secuencia + FNV-1a; el sector de cabecera se escribe último). `make pico` genera ambos en `sd/`.
     95 - GP20 (CS de la PSRAM) se deja en alto; no usar J702/UART0 para el cable.
     96 
     97 ## Ajedrez en C (`ajedrez-c/`)
     98 
     99 Port del ajedrez en Bash sobre `comun/`, con la misma arquitectura que Catan. Ver `ajedrez-c/README.md`. Plan original en `~/.claude/plans/bright-twirling-blanket.md`; falta probar el firmware en el aparato.
    100 
    101 ```sh
    102 make                          # ajedrez, ajedrez-server, ajedrez-picosim
    103 make test                     # --selftest (perft, reglas, FEN, SAN, PGN, reloj, motor, puzzles) + --sim 100 1
    104 ./ajedrez --selftest --deep   # perft completo (millones de nodos)
    105 ./ajedrez --perft N [FEN]     # divide por jugada
    106 ./ajedrez --sim N [nivel]     # partidas motor vs motor con jugadas al azar, verificando invariantes
    107 ./ajedrez --bench             # nps del motor (ojo: la máquina de Pancho suele estar cargada)
    108 ./ajedrez --uci               # el motor propio como motor UCI (opción Level 0..4; "go" a secas = límites del nivel)
    109 tools/match.py --level 4 --elo 2200 --games 40 --concurrency 6   # vs Stockfish limitado; necesita python-chess
    110 make pico                     # firmware → sd/pico1-apps/Ajedrez.uf2, sd/firmware/Ajedrez.bin, AJEDREZ.SAV/.LOG
    111 make core/book_data.c core/puzzles_data.c   # regenerar tablas (tools/mkbook.py, tools/mkpuzzles.py)
    112 python3 tools/mkpieces.py > gfx/pieces.c    # regenerar sprites (DejaVu Sans)
    113 ```
    114 
    115 - **`core/`** (C99 sin malloc ni stdio, como Catan): `chess.h` es la API. Tablero 64 casillas (a1 = 0) con mailbox 10x12 (`board.c`), jugada en 16 bits (desde|hasta|bandera), hash Zobrist incremental (la casilla al paso solo cuenta si hay captura posible). `notation.c` UCI/SAN, `pgn.c`, `opening.c` + `book_data.c`, `puzzles_data.c` (generados), `eval.c` (PeSTO), `engine.c` (PVS + quietud + TT pasada por el que llama + pila de jugadas estática `ENGINE_ARENA` porque la Pico tiene poca pila; estado de búsqueda estático: una búsqueda a la vez). `ctrl.c` es la interacción con el tablero compartida por TUI/GUI/Pico.
    116 - **Partida = posición inicial + log de acciones** (`Act`: MOVE con ms usados, RESIGN, DRAW/DRAWOK/DRAWNO, CLAIM, UNDO/UNDOOK/UNDONO, FLAG), texto `"<color> MOVE e2e4 1234"`; cabecera `"C base inc per_move undo FEN"`. `act_parse` + `act_resolve` completan las banderas contra la posición. El reloj se deriva de los ms del log (deshacer devuelve el tiempo; también se puede deshacer una partida terminada por el tablero —mate, ahogado, 5 rep., 75 jugadas, material—, no por abandono/tiempo/acuerdo); `per_move` > 0 es correspondencia. La autoridad (sesión local, servidor, PicoCalc A) pone los ms y hace caer la bandera.
    117 - **Stockfish opcional (solo Linux, `linux/uci.c`)**: nivel extra `LEVEL_STOCKFISH` (= `LEVEL_COUNT`, 1 s por jugada) que aparece en los menús solo si `stockfish_path()` lo encuentra (`$CHESS_STOCKFISH`, `engine/stockfish` junto al ejecutable, `../ajedrez/engine/stockfish`, PATH); si no arranca, juega Máximo. También lo usa "Analizar la partida". No existe en el servidor ni en la Pico (online.c sigue con 5 niveles).
    118 - **`linux/`**: `session.c` corre el motor en un pthread (juega o evalúa para la barra), guarda `.ajedrez` en `data_dir()/saves` y reescribe en cada acción el `.pgn` hermano y `data_dir()/ultima.pgn`; `app.c` menús/juego/puzzles/visor/análisis; `online.c` + `net.c` red.
    119 - **`server/chess_game.c`**: GameModule; asiento 0 = quien crea, `HDR S <asiento_blancas> <ms_turno> <cabecera>`; los bots juegan en el servidor (máx 800 ms) y sus jugadas van al log. Puerto 7374.
    120 - **`pico/app.c`**: motor en el mismo hilo (Esc lo hace jugar), guardado en `AJEDREZ.SAV`, cable A/B como Catan. RAM estática ~177 KB; la pila crece hacia SCRATCH_X y la RAM libre de arriba.
    121 - `puzzles.tsv` es propio del C (el del bash tiene jugadas ilegales y FEN inválidos); el selftest verifica que el motor resuelva todos.
    122 
    123 ## Ajedrez en Bash (`ajedrez/`)
    124 
    125 Bash 4+, `fzf`, `ncat`, `curl`; Stockfish opcional en `engine/` (`bin/get-stockfish.sh`, no es parte del proyecto).
    126 
    127 ```sh
    128 ./ches.sh --selftest   # pruebas (lib/selftest.sh)
    129 ./ches.sh              # menú; también hotseat | ai | host | join | server | resume | fen | pgn | rejoin
    130 ./bin/chess-server.sh [port]   # relay tonto (9000 por defecto)
    131 ```
    132 
    133 Lógica en `lib/` (orden de lectura: `board.sh` → `movegen.sh` → `rules.sh` → `notation.sh` → `ui.sh` → `protocol.sh` → `transport.sh` → `ai.sh` → `saves.sh`). Tablero = arreglo de 64 (índice 0 = a8). `apply_move` no valida legalidad: solo darle jugadas de `legal_moves`. Ojo con `local a=$1 b=${a}` en una línea (b ve a vacía).