Documentación Técnica · Diseño del Motor

Raumfischer: Un Motor de Ajedrez para el Raumschach

Versión 1 — Un Motor Heurístico al Estilo Fischer con Tabla de Transposición de Dos Cubetas, Siete Heurísticas Posicionales, Búsqueda de la Variación Principal y una Capa de Personalidad Fischer en la Ordenación de Movimientos
Federación Internacional de Raumschach · 2026
Raumfischer (alemán: “Fischer espacial”) es el componente de inteligencia artificial de la implementación de Raumschach en el navegador alojada en raumschach.org. Es una mejora y sustitución del motor Maackesgeist. Está escrito íntegramente en JavaScript y no requiere infraestructura de servidor. Raumfischer hereda la arquitectura de búsqueda probada de Maackesgeist — make/unmake en el lugar sin asignación de memoria, hash de Zobrist incremental, profundización iterativa con ventanas de aspiración, alfa-beta con poda de movimiento nulo, Búsqueda de la Variación Principal, búsqueda de quietud y ordenación de movimientos por asesinos e historial — al tiempo que reemplaza íntegramente la función de evaluación. La evaluación se reconstruye en torno a siete heurísticas derivadas de los principios de juego documentados de Robert James Fischer, cada una adaptada a la geometría del cubo espacial de 5×5×5: (1) material con valores recalibrados en 3D; (2) actividad y aletargamiento de piezas; (3) control del centro espacial; (4) seguridad del rey mediante rayos abiertos de piezas deslizantes y escudo de peones en 3D; (5) estructura de peones, incluidos peones pasados con bonificación cuadrática de avance, peones doblados, mayoría por nivel y penalización por peón aislado; (6) activación de torres en líneas abiertas de nivel, fila y columna; y (7) paridad del unicornio, una heurística sin análogo bidimensional. La ordenación de movimientos se amplía con una “capa de personalidad Fischer” que otorga bonificaciones a movimientos tranquilos por entrada al centro, activación de torres, avances de peones y transiciones de unicornio a la paridad del rey enemigo. La tabla de transposición se actualiza del esquema de una sola cubeta con preferencia por profundidad a un esquema de dos cubetas, subsanando la principal deficiencia estructural identificada en la hoja de ruta de Maackesgeist Versión 2. Este artículo presenta el algoritmo completo en pseudocódigo — incorporando todas las mejoras de búsqueda incluidas en la versión inicial —, documenta las siete heurísticas de evaluación junto con su justificación geométrica, identifica las preguntas abiertas restantes y describe la agenda de desarrollo para la Versión 2.

1. Introducción

El Raumschach — literalmente “ajedrez espacial” en alemán — fue inventado por el Dr. Ferdinand Maack (1861–1930) de Hamburgo y publicado por primera vez en 1907. Su versión madura se denomina “Forma Normal”, se juega en un tablero de 5×5×5 compuesto por cinco niveles de 5×5 apilados e introduce una nueva pieza, el Unicornio, que se limita a rayos de diagonal espacial (los tres ejes). La Reina combina los tres tipos de corredores. A pesar de más de un siglo de atención teórica — documentada con mayor autoridad por Anthony Dickins (1914–1987) en A Guide to Fairy Chess (1969–71) — la oposición computarizada para el Raumschach ha permanecido subdesarrollada y escasamente documentada.

Raumfischer es el sucesor de Maackesgeist como componente de inteligencia artificial de raumschach.org. Mientras que Maackesgeist estableció un marco de búsqueda técnicamente completo y una función de evaluación compacta, la contribución de Raumfischer es principalmente evaluativa: una valoración posicional de siete componentes fundamentada en los principios de juego de Robert James Fischer (1943–2008), adaptada a la geometría del ajedrez tridimensional. Este artículo se organiza del siguiente modo: §2 sitúa a Raumfischer entre los motores de Raumschach conocidos; §3 describe la representación del tablero y la geometría; §4 cubre la generación de movimientos; §5 presenta el algoritmo completo en pseudocódigo; §6 documenta la función de evaluación; §7 identifica las preguntas abiertas restantes; y §8 describe la agenda de desarrollo para la Versión 2.

La premisa de diseño de Raumfischer es que el estilo de juego de Bobby Fischer — claridad geométrica, actividad de piezas, disciplina estructural y conversión implacable — nunca fue fundamentalmente el resultado de trucos bidimensionales. Era dominio espacial y coordinación de piezas, principios que se trasladan intactos a tres dimensiones. Cada una de las siete heurísticas del §6 corresponde a un principio Fischer documentado, junto con su reformulación geométrica en 3D.

2. Raumfischer y el Panorama de Motores de Raumschach

A lo largo de los años se han puesto a disposición del público varios rivales de inteligencia artificial para el Raumschach que pueden jugarse en el navegador. La mayoría ha sido descrita como uso de minimax con poda alfa-beta; al menos uno ha sido presentado como el empleo de un algoritmo de Raumschach personalizado. Según el mejor conocimiento del presente autor, ninguno ha publicado detalles de implementación, funciones de evaluación ni pseudocódigo.

Raumfischer es, según el mejor conocimiento del presente autor, el segundo motor de Raumschach completamente documentado en el registro público — después de Maackesgeist, que estableció ese registro. El presente artículo pone a disposición una descripción técnica completa de Raumfischer que incluye representación del tablero, geometría de direcciones, generación de movimientos legales, la función de evaluación con las siete heurísticas y sus ponderaciones, y el algoritmo de búsqueda completo en pseudocódigo. Se ofrece con el mismo espíritu de reproducibilidad y conocimiento abierto que su predecesor.

En relación con Maackesgeist Versión 2, Raumfischer introduce dos categorías de cambios. La primera es estructural: la tabla de transposición se actualiza del esquema de una sola cubeta con preferencia por profundidad a un esquema de dos cubetas (preferencia por profundidad más reemplazo siempre), subsanando la principal deficiencia estructural identificada en la hoja de ruta de Maackesgeist Versión 2. La segunda es evaluativa: la función material-centralidad-movilidad de seis líneas se reemplaza íntegramente por una evaluación de siete componentes que requiere aproximadamente veinte veces más operaciones aritméticas por nodo. El resto de la infraestructura de búsqueda — make/unmake, hash de Zobrist, poda de movimiento nulo, búsqueda de quietud, profundización iterativa, ventanas de aspiración y ordenación por asesinos/historial — se hereda sin modificaciones.

3. Representación del Tablero

El tablero es un array JavaScript tridimensional board[l][r][f], donde l ∈ {0–4} indexa el nivel (A a E), r ∈ {0–4} la fila (1–5) y f ∈ {0–4} la columna (a–e). Una celda ocupada contiene un objeto plano { type, color }; una celda vacía contiene null. Los tipos de pieza son las cadenas de un solo carácter K Q R B U N P; los colores son 'w' y 'b'. Para la lógica del juego (renderizado, ejecución de movimientos, verificación de legalidad fuera del árbol de búsqueda) el tablero se clona mediante copia profunda. Dentro del árbol de búsqueda, todo el clonado se reemplaza por make/unmake en el lugar, tal como se describe en el §5.1.

3.1 Conjuntos de Direcciones Geométricas

NombreCantidadDescripciónPiezas
Ortogonal (RD)6±1 a lo largo de exactamente un ejeTorre, Reina
Diagonal de cara (BD)12±1 a lo largo de exactamente dos ejesAlfil, Reina
Diagonal espacial (UD)8±1 a lo largo de los tres ejesUnicornio, Reina
Todas (QD)26Unión de las anterioresReina, Rey

El Caballo se extiende a tres dimensiones mediante todas las permutaciones de (±2, ±1, 0), produciendo 24 direcciones de salto distintas desde una celda interior. Todos los conjuntos de direcciones se precomputan como arrays constantes al inicio. Raumfischer introduce tres ayudas precomputadas adicionales: cheby(l,r,f) (distancia de Chebyshev a Cc3, el centro geométrico del cubo); isInner(l,r,f) (prueba de pertenencia al cubo interior de 3×3×3 de 27 celdas); y uParity(l,r,f) (el valor de (l+r+f) mod 2, que determina la clase de paridad permanente de un Unicornio).

La elección de la distancia de Chebyshev en lugar de la distancia de Manhattan (taxicab) para la centralidad es deliberada y consecuente. La distancia de Chebyshev refleja el número de movimientos de Rey necesarios para alcanzar el centro, y por tanto el coste de movilidad de cualquier tipo de pieza que se mueva un paso por eje. La distancia de Manhattan penaliza en exceso las celdas de borde en exactamente un eje mientras subestima la penalización de las verdaderas celdas esquina del cubo; la distancia de Chebyshev distribuye correctamente la penalización para una pieza que se mueve en las 26 direcciones.

4. Generación de Movimientos

La generación de movimientos se mantiene sin cambios respecto a Maackesgeist Versión 2 en el nivel pseudolegal. pieceMoves(board, l, r, f) genera movimientos pseudolegales para una sola pieza mediante trazado de rayos (piezas deslizantes) o enumeración de un solo paso (Rey, Caballo, Peón). allPseudo(board, color) agrega todos los movimientos de las piezas de un color dado. legalMoves(board, color) filtra los movimientos pseudolegales mediante make/unmake: cada movimiento candidato se aplica en el lugar, se prueba si el Rey del bando que mueve está en jaque mediante isAttacked y el movimiento se deshace de inmediato — sin producir asignaciones de memoria en el montón. isAttacked(board, cell, byColor) realiza trazado de rayos inverso desde la celda objetivo. Todo el árbol de búsqueda, desde la raíz hasta la hoja de quietud, está libre de asignaciones de memoria.

La promoción de peones se maneja dentro de pieceMoves: cuando un peón alcanza la celda de promoción (nivel E, fila 5 para las Blancas; nivel A, fila 1 para las Negras), el movimiento se emite cinco veces con prom configurado en cada uno de Q R B U N. El jugador humano selecciona la pieza de promoción mediante una ventana modal; el motor siempre promueve a Reina (la pieza de mayor valor) en su propia generación de movimientos, dado que getBestMove evalúa todos los movimientos legales, incluidas las cinco variantes de promoción.

5. El Algoritmo

5.1 Make / Unmake

Raumfischer hereda el esquema de mutación en el lugar sin asignación de memoria de Maackesgeist. makeMove(board, m) mueve el objeto pieza directamente dentro del array del tablero y devuelve un registro de deshacer suficiente para revertir la operación. unmakeMove(board, m, undo) restaura el tablero a su estado anterior usando ese registro. La promoción se maneja reemplazando el objeto pieza en el destino; unmake restaura el objeto peón original.

El registro de deshacer se amplía para incluir datos de movilidad en caché mediante un array paralelo mobCache[l][r][f], que almacena el recuento de movimientos pseudolegales de cada celda ocupada. En makeMove, las entradas de caché para el origen, el destino y todas las celdas cuyo rayo de pieza deslizante atraviesa cualquiera de las dos casillas se invalidan y se recalculan. En unmakeMove, los valores anteriores se restauran desde el registro de deshacer. Esto convierte el coste dominante de evaluación del §6.2 de O(piezas × longitud de rayo) por llamada a O(celdas invalidadas) por make/unmake — una aceleración sustancial sin coste para la corrección.

FUNCTION makeMove(board, m)  undoRecord:
  captured  board[m.to]
  piece     board[m.from]
  IF m.prom:
    board[m.to]  {type: m.prom, color: piece.color}
  ELSE:
    board[m.to]  piece
  board[m.from]  null
  mobUndo  updateMobCache(board, m)  // invalida y recalcula las celdas afectadas
  RETURN {captured, piece, mobUndo}

FUNCTION unmakeMove(board, m, undo):
  board[m.from]  undo.piece     // restaura la pieza que se mueve (maneja la promoción)
  board[m.to]    undo.captured  // restaura la pieza capturada (o null)
  restoreMobCache(undo.mobUndo)   // restaura los recuentos de movimientos en caché desde el registro de deshacer

5.2 Hash de Zobrist

Un generador congruencial lineal determinista con semilla (semilla 0xDEADBEEF, multiplicador 1664525, sumando 1013904223) produce 1.750 pares de claves pseudoaleatorias, cada uno emulando un entero de 64 bits como dos mitades de 32 bits sin signo (hi y lo). Los enteros nativos de JavaScript son de 32 bits; la emulación de 64 bits reduce la probabilidad de una colisión de hash espuria — en la que dos posiciones distintas comparten el mismo índice de tabla y el mismo fragmento de clave — a niveles despreciables en las profundidades que Raumfischer busca. El hash raíz se computa desde cero una vez por decisión de movimiento iterando las 125 celdas. Todos los hashes posteriores dentro del árbol de búsqueda se actualizan incrementalmente en O(1): hashMove aplica XOR para eliminar la pieza que se mueve en su origen, aplica XOR para eliminar la pieza capturada en el destino, aplica XOR para añadir la pieza que se mueve (o su promoción) en el destino, y alterna la clave de turno. Deshacer no requiere trabajo de hash porque el hash padre se pasa hacia abajo por la pila de llamadas y el hash hijo se descarta cuando la llamada recursiva retorna.

FUNCTION hashBoard(board, color)  hash:
  h  SIDE_KEY if color = Black else 0
  FOR EACH occupied cell (l, r, f):
    h  h XOR ZKEYS[piece.type, piece.color, l, r, f]
  RETURN h (unsigned 32-bit)

FUNCTION hashMove(hash, undo, m)  hash:    // actualización incremental O(1)
  hash  hash XOR ZKEYS[undo.piece.type, undo.piece.color, m.from]
  IF undo.captured:
    hash  hash XOR ZKEYS[undo.captured.type, undo.captured.color, m.to]
  hash  hash XOR ZKEYS[m.prom OR undo.piece.type, undo.piece.color, m.to]
  RETURN hash XOR SIDE_KEY (unsigned 32-bit)

5.3 Tabla de Transposición de Dos Cubetas

Raumfischer actualiza la tabla de transposición de una sola cubeta con preferencia por profundidad de Maackesgeist a un esquema de dos cubetas, subsanando la Prioridad 1 de la hoja de ruta de mejoras de Maackesgeist Versión 2. Se mantienen en paralelo dos arrays planos de 131.072 entradas (217): una cubeta de preferencia por profundidad (ttDeep) que retiene la entrada más profunda encontrada hasta el momento para cada posición, y una cubeta de reemplazo siempre (ttAlways) que sobreescribe incondicionalmente con la entrada calculada más recientemente. Ambos arrays se indexan por hash & TT_MASK.

Cada entrada almacena el hash, la profundidad de búsqueda, la puntuación, un indicador (EXACT / LOWER / UPPER) y el mejor movimiento encontrado en ese nodo. En la consulta, se comprueba primero ttDeep; si no hay coincidencia, se comprueba ttAlways. En el almacenamiento, ttAlways siempre se escribe; ttDeep solo se escribe cuando la nueva entrada es al menos tan profunda como la existente. Esto garantiza que los valiosos resultados profundos nunca se descartan, al tiempo que asegura que la tabla permanece sensible a las posiciones calculadas recientemente — la patología conocida del esquema puro de preferencia por profundidad.

Tanto ttDeep como ttAlways se reinician a vacío al comienzo de cada nueva partida, antes de que se llame a getBestMove por primera vez. Esto elimina cualquier riesgo de contaminación cruzada entre partidas en la tabla de transposición: un acierto de ttProbe en una nueva partida no puede devolver puntuaciones en caché de un contexto estratégico diferente establecido en una partida anterior. El coste — una caché fría para los movimientos de apertura — es despreciable en la práctica, ya que la tabla se calienta rápidamente en la primera decisión de movimiento.

FUNCTION ttProbe(hash)  entry | null:
  i  hash AND TT_MASK
  IF ttDeep[i] exists AND ttDeep[i].hash = hash:   RETURN ttDeep[i]
  IF ttAlways[i] exists AND ttAlways[i].hash = hash: RETURN ttAlways[i]
  RETURN null

FUNCTION ttStore(hash, depth, score, flag, bestMove):
  i      hash AND TT_MASK
  entry  {hash, depth, score, flag, bestMove}
  ttAlways[i]  entry                              // incondicional
  IF ttDeep[i] is empty OR ttDeep[i].depth ≤ depth:
    ttDeep[i]  entry                             // preferencia por profundidad

5.4 Ordenación de Movimientos: La Capa de Personalidad Fischer

La ordenación de movimientos sigue la misma pila de prioridades de cuatro niveles que Maackesgeist: (1) mejor movimiento de la TT, (2) capturas ordenadas por MVV/LVA, (3) ranura de movimiento asesino 0, (4) ranura de movimiento asesino 1. Raumfischer amplía el quinto nivel — la puntuación de la heurística de historial para movimientos tranquilos — con un conjunto de bonificaciones derivadas de Fischer que se aplican antes de la ordenación. Estas bonificaciones reflejan los movimientos que Fischer preferiría por razones posicionales, con independencia de las secuencias tácticas forzadas.

PrioridadCondiciónPuntuación
1Mejor movimiento de la TT2.000.000
2Captura (MVV/LVA: valor de la víctima × 10 − valor del atacante)1.000.000 + Δ
3Ranura de movimiento asesino 0 en la semijugada actual900.000
4Ranura de movimiento asesino 1 en la semijugada actual890.000
5aEntrada al centro: destino en el cubo interior de 3×3×3hist + 55
5bEntrada al centro: destino exactamente en Cc3hist + 110
5cTorre: destino en el nivel inicial del enemigohist + 75
5dTorre: destino en línea abierta de nivel/fila/columnahist + 30–35
5eAvance de peón: adv × 18 (adv = progreso de nivel + fila hacia la promoción)hist + 0–144
5fUnicornio: destino coincide con la clase de paridad del rey enemigohist + 45
5gHeurística de historial (profundidad² acumulada en cortes beta)0–32.000
FUNCTION scoreMv(board, m, ply, ttBest)  integer:
  IF m = ttBest: RETURN 2000000
  victim  board[m.to]
  IF victim exists:
    attacker  board[m.from]
    RETURN 1000000 + VALUE[victim.type] × 10 − VALUE[attacker.type]
  IF m = killers[ply][0]: RETURN 900000
  IF m = killers[ply][1]: RETURN 890000
  // capa de personalidad Fischer sobre la puntuación de historial
  s  history[m.from × 125 + m.to]
  mover  board[m.from]
  IF isInner(m.to): s += 55
  IF m.to = Cc3:    s += 110
  IF mover = Rook:
    IF m.to.level = enemyHomeLevel:     s += 75
    IF isOpenLine(board, m.to, LEVEL):   s += 35
    IF isOpenLine(board, m.to, RANK):    s += 30
    IF isOpenLine(board, m.to, FILE):    s += 30
  IF mover = Pawn:
    adv  (level + rank progress toward promotion)
    s += adv × 18
  IF mover = Unicorn:
    IF uParity(m.to) = uParity(enemyKing): s += 45
  RETURN s

5.5 Búsqueda de Quietud

En profundidad cero, la búsqueda entra en quietud en lugar de llamar directamente a evalBoard. La búsqueda de quietud calcula una puntuación stand-pat a partir de la evaluación estática y luego amplía la búsqueda a través de todas las capturas disponibles (hasta 6 semijugadas adicionales) hasta que no queden capturas. Esto elimina el efecto horizonte: el motor no confunde una posición tácticamente inestable con una tranquila simplemente porque cae en el límite nominal de búsqueda. La poda stand-pat se aplica en ambas direcciones: si la puntuación estática ya satisface la condición de corte, las capturas no necesitan explorarse.

Dentro del bucle de capturas se aplican dos extensiones. La poda delta omite cualquier captura en la que ni siquiera la ganancia máxima posible de material — el valor de la víctima más un margen de seguridad — pueda elevar la puntuación por encima de alfa; las promociones y los movimientos que dan jaque quedan siempre exentos. Las extensiones de jaque otorgan una semijugada adicional de búsqueda de quietud cuando el bando que mueve da jaque, hasta un máximo de dos semijugadas adicionales más allá del presupuesto nominal de quietud; esto previene la mala evaluación de secuencias forzadas que avanzan mediante jaques en lugar de capturas.

FUNCTION quiesce(board, alpha, beta, isMaximizing, qdepth)  score:
  standPat  evalBoard(board)
  IF qdepth = 0: RETURN standPat
  IF isMaximizing:
    IF standPat ≥ beta:  RETURN standPat  // corte stand-pat
    alpha  max(alpha, standPat)
  ELSE:
    IF standPat ≤ alpha: RETURN standPat  // corte stand-pat
    beta  min(beta, standPat)
  captures  allPseudo(board, color) FILTERED TO captures only
  IF captures is empty: RETURN standPat
  orderMoves(board, captures, MAX_PLY−1, null)
  best  standPat
  FOR EACH cap IN captures:
    // Poda delta: omitir si ni la mejor ganancia posible alcanza alfa
    victim  board[cap.to]
    IF NOT cap.prom:
      IF isMaximizing AND standPat + VALUE[victim.type] + DELTA_MARGIN < alpha: CONTINUE
      IF NOT isMaximizing AND standPat − VALUE[victim.type] − DELTA_MARGIN > beta: CONTINUE
    undo  makeMove(board, cap)
    IF inCheck(board, color): unmakeMove(board, cap, undo); CONTINUE
    // Extensión de jaque: +1 semijugada cuando este movimiento da jaque (acotado en qdepth ≤ CHECK_EXT_MAX)
    ext  1 if inCheck(board, opponent) AND qdepth ≤ CHECK_EXT_MAX else 0
    score  quiesce(board, alpha, beta, NOT isMaximizing, qdepth − 1 + ext)
    unmakeMove(board, cap, undo)
    IF isMaximizing:
      best  max(best, score); alpha  max(alpha, best)
      IF alpha ≥ beta: BREAK
    ELSE:
      best  min(best, score); beta  min(beta, best)
      IF beta ≤ alpha: BREAK
  RETURN best

5.6 Minimax con Alfa-Beta, Poda de Movimiento Nulo, Reducciones de Movimientos Tardíos y Poda de Futilidad

La búsqueda recibe el hash actual del tablero como parámetro en lugar de recalcularlo en cada nodo. Cada llamada recursiva deriva el hash hijo mediante hashMove (O(1)). La poda de movimiento nulo se aplica antes del bucle de movimientos: si la posición no está en jaque, no es exclusivamente Rey y Peones (lo que arriesga un corte espurio por zugzwang) y la profundidad restante es al menos 3, el motor cede al oponente un movimiento libre alternando la clave de turno sin alterar el tablero y luego busca a profundidad−3 con una ventana de un movimiento de ancho. Un corte a esta profundidad reducida indica que la posición ya es lo suficientemente sólida como para que una búsqueda completa sea innecesaria. El indicador nullOk evita movimientos nulos consecutivos.

Dos técnicas de poda actúan dentro del bucle de movimientos. La poda de futilidad, aplicada en profundidades 1 y 2, omite los movimientos tranquilos sin promoción cuando la evaluación estática de la posición actual más un margen (250 cp en profundidad 1; 450 cp en profundidad 2) no puede alcanzar alfa; las capturas, las promociones y los movimientos hacia el jaque quedan siempre exentos. Las Reducciones de Movimientos Tardíos (LMR) se aplican después de los primeros k = 4 movimientos a profundidad completa: los movimientos tranquilos subsiguientes ordenados se buscan a profundidad−2 con una ventana nula [alfa, alfa+1]; una nueva búsqueda a profundidad completa se activa solo cuando el resultado reducido falla alto. Ambas técnicas interactúan con la ordenación de movimientos de la capa de personalidad Fischer, que garantiza que los mejores candidatos aparezcan al inicio de la lista, y son especialmente valiosas dado el alto factor de ramificación del tablero 3D.

FUNCTION minimax(board, depth, alpha, beta, isMaximizing, ply, hash, nullOk=true)  score:
  color   White if isMaximizing else Black
  entry   ttProbe(hash)
  ttBest  entry.bestMove if entry exists else null
  // Corte de tabla de transposición
  IF entry exists AND entry.depth ≥ depth:
    IF entry.flag = EXACT: RETURN entry.score
    IF entry.flag = LOWER: alpha  max(alpha, entry.score)
    IF entry.flag = UPPER: beta   min(beta,  entry.score)
    IF alpha ≥ beta: RETURN entry.score
  // Nodo hoja: entrar en búsqueda de quietud
  IF depth = 0: RETURN quiesce(board, alpha, beta, isMaximizing, 6)
  // Poda de movimiento nulo (R = 3)
  IF nullOk AND depth ≥ 3 AND NOT inCheck(board, color)
              AND NOT onlyKingAndPawns(board, color):
    nmScore  minimax(board, depth−3, beta−1, beta,
                        NOT isMaximizing, ply+1, hash XOR SIDE_KEY, false)
    IF isMaximizing AND nmScore ≥ beta:  RETURN beta
    IF NOT isMaximizing AND nmScore ≤ alpha: RETURN alpha
  moves  legalMoves(board, color)
  IF moves is empty:
    IF inCheck(board, color):
      RETURN (−20000 − depth×50) if isMaximizing    // jaque mate espacial contra nosotros
      RETURN (+20000 + depth×50) if NOT isMaximizing // jaque mate espacial a nuestro favor
    RETURN 0  // ahogado
  orderMoves(board, moves, ply, ttBest)
  staticEval  evalBoard(board)  // para la compuerta de poda de futilidad
  origAlpha  alpha
  best      −∞ if isMaximizing else +∞
  bestMove  moves[0];  cutoff  false
  FOR EACH m IN moves (moveIndex = 0, 1, …):
    // Poda de futilidad: omitir movimientos tranquilos en profundidad 1–2 cuando la posición es claramente perdida
    IF depth ≤ 2 AND NOT isCapture(m) AND NOT m.prom AND NOT inCheck(board, color):
      IF isMaximizing AND staticEval + FUTILITY_MARGIN[depth] ≤ alpha: CONTINUE
      IF NOT isMaximizing AND staticEval − FUTILITY_MARGIN[depth] ≥ beta:  CONTINUE
    undo       makeMove(board, m)
    childHash  hashMove(hash, undo, m)
    // Reducciones de Movimientos Tardíos: reducir movimientos tranquilos no críticos después de los primeros k
    IF moveIndex ≥ LMR_THRESHOLD AND depth ≥ 3
       AND NOT isCapture(m) AND NOT m.prom AND NOT inCheck(board, color):
      score  minimax(board, depth−2, alpha, alpha+1,
                          NOT isMaximizing, ply+1, childHash, false)
      IF (isMaximizing AND score ≤ alpha) OR (NOT isMaximizing AND score ≥ beta):
        unmakeMove(board, m, undo); moveIndex++; CONTINUE  // omitir la nueva búsqueda completa
    score  minimax(board, depth−1, alpha, beta,
                      NOT isMaximizing, ply+1, childHash)
    unmakeMove(board, m, undo)
    IF isMaximizing:
      IF score > best: best  score; bestMove  m
      alpha  max(alpha, best)
    ELSE:
      IF score < best: best  score; bestMove  m
      beta  min(beta, best)
    IF beta ≤ alpha:
      IF move is quiet:
        addKiller(m, ply)
        history[m]  min(32000, history[m] + depth²)
      cutoff  true; BREAK
    moveIndex++
  flag  LOWER if cutoff else (UPPER if best ≤ origAlpha else EXACT)
  ttStore(hash, depth, best, flag, bestMove)
  RETURN best

5.7 Profundización Iterativa con Ventanas de Aspiración y Búsqueda de la Variación Principal

Cada pasada de profundidad se envuelve en una ventana de aspiración estrecha centrada en la puntuación devuelta por la pasada anterior. Para profundidad 1, se usa una ventana completa [−∞, +∞] ya que no existe puntuación previa. A partir de profundidad 2, la ventana es [prevScore−50, prevScore+50]. Si la búsqueda devuelve una puntuación fuera de esa ventana (un fallo por debajo o por encima), la pasada se vuelve a buscar de inmediato con anchura completa. La nueva búsqueda de anchura completa se beneficia de la mejor ordenación de movimientos y de las entradas de TT depositadas por la pasada estrecha fallida, por lo que su coste es modesto. El hash raíz se computa una vez desde cero; todas las llamadas recursivas derivan los hashes hijo de forma incremental mediante hashMove. Las posiciones del Rey se almacenan en caché una vez por llamada raíz en _wKingCache y _bKingCache para uso de la capa de personalidad Fischer dentro de scoreMv sin activar exploraciones adicionales del tablero dentro del comparador de ordenación.

La Búsqueda de la Variación Principal (PVS) se aplica dentro de searchRoot. El primer movimiento raíz se busca con la ventana de aspiración completa [alfa, hi] para establecer la variación principal. Cada movimiento subsiguiente se sondea primero con una ventana nula [alfa, alfa+1]. Una puntuación que supera estrictamente alfa pero cae por debajo de hi demuestra que el movimiento mejora genuinamente sobre el mejor actual y escapó al sondeo de ventana nula; solo entonces se activa una nueva búsqueda de ventana completa [alfa, hi]. Alfa se actualiza después de cada mejora, por lo que cada sondeo de ventana nula subsiguiente se realiza contra la cota inferior más ajustada disponible. Dada la sólida ordenación de movimientos de Raumfischer — mejor movimiento de la TT, MVV/LVA, asesinos y la capa de personalidad Fischer — el primer movimiento es correcto con suficiente frecuencia para que los sondeos de ventana nula para los movimientos restantes confirmen rápidamente y no se requiera ninguna nueva búsqueda. El cambio de implementación está localizado íntegramente en searchRoot y no afecta a minimax, quiesce ni a la función de evaluación.

FUNCTION getBestMove(board, color, maxDepth)  move:
  moves  legalMoves(board, color)
  IF moves is empty: RETURN null
  IF maxDepth = 0: RETURN randomChoice(moves)  // Nivel Principiante
  // Reiniciar asesinos; envejecer historial; almacenar posiciones del Rey en caché para scoreMv
  killers[0..MAX_PLY]  [null, null]
  history[all]         history[all] >> 1
  _wKingCache  findKing(board, White)
  _bKingCache  findKing(board, Black)
  isMaximizing  (color = White)
  rootHash      hashBoard(board, color)   // calculado una vez
  bestMove      moves[0];  prevScore  0
  FUNCTION searchRoot(d, lo, hi)  {bs, bm}:
    bs     −∞ if isMaximizing else +∞;  bm  moves[0]
    alpha  lo
    FOR EACH m IN moves (moveIndex = 0, 1, …):
      undo   makeMove(board, m)
      ch     hashMove(rootHash, undo, m)
      IF moveIndex = 0:
        // Movimiento VP: búsqueda de ventana completa para establecer la variación principal
        score  minimax(board, d−1, alpha, hi, NOT isMaximizing, 1, ch)
      ELSE:
        // PVS: sondeo de ventana nula — verificar que el movimiento no es mejor que el actual mejor
        score  minimax(board, d−1, alpha, alpha+1, NOT isMaximizing, 1, ch)
        IF score > alpha AND score < hi:
          // Fallo alto en ventana nula: mejora genuina; nueva búsqueda con anchura completa
          score  minimax(board, d−1, alpha, hi, NOT isMaximizing, 1, ch)
      unmakeMove(board, m, undo)
      IF isMaximizing AND score > bs: bs  score; bm  m; alpha  bs
      IF NOT isMaximizing AND score < bs: bs  score; bm  m
    RETURN {bs, bm}
  FOR d  1 TO maxDepth:
    orderMoves(board, moves, 0, bestMove)  // inicializar con el mejor de la última iteración
    IF d = 1:
      result  searchRoot(d, −∞, +∞)    // ventana completa en la primera pasada
    ELSE:
      lo  prevScore − 50;  hi  prevScore + 50
      result  searchRoot(d, lo, hi)
      IF result.bs ≤ lo OR result.bs ≥ hi:  // aspiración fallida
        result  searchRoot(d, −∞, +∞)
    prevScore  result.bs;  bestMove  result.bm
  RETURN bestMove

5.8 Niveles de Dificultad

NivelNombreProf. Máx.Característica
0Principiante0Movimiento legal aleatorio; sin búsqueda
1Débil2Profundización iterativa a 2 semijugadas + quietud
2Intermedio3Profundización iterativa a 3 semijugadas + quietud
3Fuerte4Profundización iterativa a 4 semijugadas + quietud
4RSM5Profundización iterativa a 5 semijugadas + quietud
5GRSM6Profundización iterativa a 6 semijugadas + quietud

6. Función de Evaluación — Las Siete Heurísticas Fischer

La función de evaluación devuelve una puntuación en centipeones desde la perspectiva de las Blancas. Es el lugar exclusivo de la contribución original de Raumfischer: siete heurísticas derivadas de los principios de juego documentados de Fischer, cada una reformulada en términos de la geometría del cubo espacial de 5×5×5. La función itera las 125 celdas una vez para recopilar todas las piezas y localizar ambos reyes, y luego evalúa cada heurística en un único recorrido sobre la lista de piezas recopiladas. El coste computacional dominante corresponde a la heurística §6.2, que llama a pieceMoves para cada pieza a fin de determinar su movilidad.

§6.1 — Material con Valores Recalibrados en 3D

Fischer tomó el material con precisión clínica y casi nunca sacrificó sin compensación concreta. Los valores materiales se recalibran respecto a Maackesgeist para reflejar la geometría del movimiento tridimensional.

PiezaRaumfischer (cp)Maackesgeist (cp)Justificación
Peón100100Sin cambios
Caballo51035024 destinos de salto desde una celda interior (vs 8 en 2D); inmunidad al bloqueo de líneas
Alfil56035012 direcciones de rayo; confinado a 1/4 del tablero en los planos nivel-columna-fila
Unicornio290400Único corredor puramente 3D; cubre solo 30 de 125 celdas a lo largo del tiempo; estructuralmente único
Torre4605006 direcciones de rayo; ligeramente reducido respecto a la ortodoxia 2D por el tablero compacto
Reina16201200Combina las 26 direcciones; dominante en el espacio 3D

Cada pieza recibe también una bonificación posicional basada en su distancia de Chebyshev al centro geométrico Cc3 = (2,2,2) y en la ocupación del Nivel C ecuatorial (l = 2).

v ← MAT[type] + cen×wtype + (l=2 ? lvltype : 0)

donde cen = 2 − cheby(l,r,f) toma valores 0, 1 o 2, y los pesos por tipo wtype oscilan entre 25 (Alfil) y 80 (Reina). El Unicornio recibe +25 adicionales por ocupar cualquier celda dentro del cubo interior de 3×3×3, lo que refleja su mayor movilidad desde esa zona.

§6.2 — Actividad de Piezas: Movilidad y Aletargamiento

«Cada pieza debe trabajar.» Fischer mejoraba constantemente su peor pieza, y era rápido en reconocer que una pieza sin casilla buena era una responsabilidad estructural. En el Raumschach, este principio es geométricamente agudo: una Torre en la celda esquina Aa1 controla solo 8 celdas a lo largo de 3 rayos; la misma Torre en Cc3 controla 24 celdas a lo largo de 6 rayos. Las celdas de borde y esquina del cubo imponen severas penalizaciones de movilidad a todos los tipos de pieza.

Bonificación de movilidad: +4 centipeones por movimiento pseudolegal. Este término se calcula a partir de pieceMoves (que ya se llama durante la generación de movimientos legales) y recompensa las piezas activas que ejercen influencia sobre una gran parte del tablero.

Penalización por aletargamiento: cuando la movilidad de una pieza cae por debajo de un umbral específico por tipo, se aplica una penalización de −8 centipeones por movimiento por debajo del umbral. Esto afecta con mayor intensidad a las piezas deslizantes enterradas en las esquinas y bordes del cubo.

PiezaUmbral de aletargamiento
Torre4 movimientos pseudolegales
Alfil5 movimientos pseudolegales
Unicornio4 movimientos pseudolegales
Reina10 movimientos pseudolegales
Caballo5 movimientos pseudolegales
Peón1 movimiento pseudolegal
§6.3 — Control del Centro Espacial

Fischer dominaba el centro antes que casi cualquier otra cosa. El centro tridimensional es el cubo interior de 3×3×3 de 27 celdas, con Cc3 en su núcleo. Controlar esta zona maximiza simultáneamente la movilidad de todos los tipos de pieza.

Se otorgan dos bonificaciones por pieza:

Ocupación del cubo interior: +12 cp por cualquier pieza físicamente situada dentro del cubo interior de 3×3×3 (es decir, todas las coordenadas en {1,2,3}).

Bonificación del ecuador del Nivel C: +6 cp por cualquier pieza en el Nivel C (l = 2), el plano ecuatorial espacial. Este es el análogo 3D de ocupar la cuarta y quinta fila en el ajedrez ortodoxo.

§6.4 — Seguridad del Rey

Fischer era obsesivo respecto a la seguridad del rey. En el Raumschach, el rey enfrenta hasta 26 direcciones de ataque (contra 8 en el ajedrez ortodoxo), lo que hace que la exposición del rey sea dramáticamente más peligrosa. Se calculan dos sub-heurísticas.

(a) Rayos abiertos de piezas deslizantes: Para cada rey, el motor traza las 26 direcciones de rayo hacia el exterior desde la celda del rey. Se cuenta cada rayo sin obstáculos que termina en una pieza deslizante enemiga (Torre en rayo ortogonal, Alfil en rayo de diagonal de cara, Unicornio o Reina en rayo de diagonal espacial). Cada rayo de este tipo incurre en una penalización de −20 cp.

(b) Escudo de peones tridimensional: Los peones amigos dentro de ±1 nivel y ±2 columnas del rey proporcionan un escudo espacial. Cada uno de esos peones añade +10 cp a la seguridad del rey. El radio del escudo se amplía deliberadamente en el eje de nivel (el eje del avance de los peones) para reflejar que un peón que se aproxima desde abajo o desde arriba proporciona cobertura significativa en 3D.

§6.5 — Estructura de Peones

Fischer tenía la más fina intuición sobre peones en la historia del ajedrez, y trataba los avances de peones como inversiones estratégicas a largo plazo. Se aplican tres sub-heurísticas.

(a) Peones pasados: Un peón está pasado en 3D si ningún peón enemigo puede interceptarlo en ninguna combinación futura de (nivel, fila), dentro de ±1 columna. La bonificación es cuadrática en el avance — lo que refleja la comprensión de Fischer de que los peones pasados se vuelven exponencialmente más peligrosos a medida que se acercan a la promoción:

bonus ← 30 + adv² × 5

donde adv = l + r para las Blancas y adv = (4−l) + (4−r) para las Negras, con un rango de 0 en la fila inicial a 8 en la celda de promoción.

(b) Peones doblados: Dos peones del mismo color que comparten la misma posición (columna, fila) en niveles diferentes no pueden desbloquearse mutuamente y están estructuralmente congelados. Cada uno de esos pares incurre en una penalización de −20 cp.

(c) Mayoría de peones por nivel: Tener más peones que el oponente en un nivel determinado crea el potencial para un peón pasado en ese nivel. Cada unidad de mayoría en los cinco niveles puntúa +9 cp.

(d) Peones aislados: Un peón está aislado en 3D si ningún peón amigo ocupa ningún otro nivel mientras comparte su columna o su fila — la generalización 3D de la definición clásica de peón aislado. En concreto, un peón en (l, r, f) está aislado cuando no existe ningún peón amigo en ningún (l′, r′, f) con l′ ≠ l (misma columna de archivo, cualquier fila, cualquier otro nivel) ni ningún peón amigo en ningún (l′, r, f′) con l′ ≠ l (misma columna de fila, cualquier archivo, cualquier otro nivel). Un peón así no tiene apoyo estructural a lo largo de ninguno de los dos ejes planos a través de los cuales podría ser atacado o defendido, una vulnerabilidad que es geométricamente más grave en 3D que en el ajedrez ortodoxo porque el ataque puede originarse desde cualquiera de las tres dimensiones. Cada peón aislado incurre en una penalización de −15 cp.

§6.6 — Activación de Torres

Las torres de Fischer siempre estaban en columnas abiertas o en la séptima fila. En el Raumschach, las torres se mueven a lo largo de tres ejes independientes — columnas de nivel, columnas de fila y columnas de archivo — y el concepto de línea abierta se generaliza a los tres. El análogo de la «7ª fila» es el nivel inicial del enemigo.

Para cada Torre, se aplican las siguientes bonificaciones:

CondiciónBonificación
Torre en línea de nivel abierta (sin peones en la columna de nivel)+22 cp
Torre en línea de fila abierta (sin peones en la columna de fila)+18 cp
Torre en línea de columna abierta (sin peones en la columna de archivo)+18 cp
Torre en el nivel inicial del enemigo (E para las Blancas, A para las Negras)+45 cp
Dos torres compartiendo cualquier línea alineada con un eje (torres dobladas)+22 cp

La bonificación por línea de nivel (+22 cp) es deliberadamente la mayor de las tres bonificaciones de línea abierta, lo que refleja la apreciación de que el eje vertical es el tipo de línea más desaprovechado en el Raumschach: es la única línea de torre que atraviesa los cinco niveles simultáneamente, otorgando a la torre influencia sobre toda la profundidad del tablero desde una sola posición.

§6.7 — Paridad del Unicornio

Esta heurística no tiene análogo bidimensional y es el elemento estructuralmente más original de la evaluación de Raumfischer.

El Unicornio se mueve exclusivamente a través de las esquinas de celdas: cada paso cambia las tres coordenadas en ±1. De ello se deduce que la paridad de (l + r + f) es invariante en todos los movimientos del Unicornio. Un Unicornio que comienza en una celda donde (l+r+f) es par nunca puede alcanzar una celda donde (l+r+f) es impar, y viceversa. Esto confina cada Unicornio permanentemente a exactamente 30 de las 125 celdas — la mitad del tablero. Es el análogo 3D del confinamiento de color del alfil en el ajedrez ortodoxo, pero más severo: un alfil está confinado a la mitad de las celdas de un color dado, mientras que el Unicornio está confinado a una clase de paridad global de la suma de coordenadas tridimensional.

De esta geometría se derivan dos sub-heurísticas:

(a) Coincidencia de paridad del rey: Fischer identificaría inmediatamente qué clase de paridad ocupa el rey enemigo y pondría en juego un Unicornio de paridad coincidente. Cada Unicornio propio cuya clase de paridad coincida con la del rey enemigo recibe una bonificación de +35 cp.

(b) Par de unicornios malo: Si los dos Unicornios de un jugador comparten la misma clase de paridad, entre ambos cubren solo la mitad del tablero — el análogo directo del «par de alfiles malos» en el que ambos alfiles están confinados al mismo color. Esta debilidad estructural se penaliza con −45 cp (aplicado una vez por color, no por Unicornio).

uParity(l, r, f) ← (l + r + f) mod 2

La función de evaluación completa en pseudocódigo:

FUNCTION evalBoard(board)  score (centipeones, perspectiva de las Blancas):
  score  0
  pieces  []; wKing  null; bKing  null
  FOR EACH occupied cell (l, r, f):
    pieces.push({p, l, r, f})
    IF p.type = King: record position in wKing or bKing
  IF wKing or bKing is null: RETURN 0  // terminal / ilegal

  // §6.1 Material + bonificaciones posicionales
  FOR EACH {p, l, r, f} IN pieces:
    cen  2 − cheby(l, r, f)       // centralidad de Chebyshev, ∈ {0, 1, 2}
    v    MAT[p.type] + cen × w[p.type] + levelBonus(l, p.type)
    IF p.type = Unicorn AND isInner(l,r,f): v += 25
    score += sign(p.color) × v

  // §6.2 Actividad de piezas: movilidad y aletargamiento
  FOR EACH {p, l, r, f} IN pieces (excluding Kings):
    mob     |pieceMoves(board, l, r, f)|
    score  += sign(p.color) × mob × 4
    thresh  DORMANCY[p.type]
    IF mob < thresh: score += sign(p.color) × (mob − thresh) × 8

  // §6.3 Centro espacial
  FOR EACH {p, l, r, f} IN pieces:
    IF isInner(l,r,f): score += sign(p.color) × 12
    IF l = 2:         score += sign(p.color) × 6

  // §6.4 Seguridad del rey
  FOR EACH color IN {White, Black}:
    [kl, kr, kf]  king position
    rays     openRaysOnKing(kl, kr, kf, opponent)
    shelter  friendly pawns with |Δl| ≤ 1 AND |Δf| ≤ 2
    score   += sign(color) × (shelter × 10 − rays × 20)

  // §6.5 Estructura de peones
  FOR EACH color IN {White, Black}:
    FOR EACH own pawn at (l, r, f):
      IF isPassed3D(pawn, oppPawns):
        adv  advancement; score += sign × (30 + adv² × 5)
      IF doubled (same file+rank, different level): score += sign × (−20)
      // Peón aislado: sin peón amigo en la misma columna de archivo o columna de fila en ningún otro nivel
      fileSupport  EXISTS own pawn at (l′, r′, f) with l′ ≠ l
      rankSupport  EXISTS own pawn at (l′, r, f′) with l′ ≠ l
      IF NOT fileSupport AND NOT rankSupport: score += sign × (−15)
    FOR EACH level: score += (wPawnCount − bPawnCount) × 9

  // §6.6 Activación de torres
  FOR EACH Rook at (l, r, f):
    IF isOpenLine(board, l,r,f, LEVEL):  score += sign × 22
    IF isOpenLine(board, l,r,f, RANK):   score += sign × 18
    IF isOpenLine(board, l,r,f, FILE):   score += sign × 18
    IF l = enemyHomeLevel:                 score += sign × 45
  IF doubled rooks share an axis-line:     score += sign × 22

  // §6.7 Paridad del unicornio
  FOR EACH color IN {White, Black}:
    kPar  uParity(enemyKing)
    FOR EACH own Unicorn at (l,r,f):
      IF uParity(l,r,f) = kPar: score += sign × 35
    IF both own Unicorns share parity:     score += sign × (−45)

  RETURN score

7. Preguntas Abiertas

La infraestructura de búsqueda incorporada durante el desarrollo de la Versión 1 — movilidad en caché, Búsqueda de la Variación Principal, reducciones de movimientos tardíos, poda de futilidad, extensiones de jaque y poda delta en la búsqueda de quietud, emulación de Zobrist de 64 bits y vaciado de la tabla de transposición entre partidas — resolvió todas las debilidades de búsqueda previamente identificadas. Quedan dos preguntas abiertas de fondo.

La gestión de búsqueda por tiempo se evaluó como posible mejora y se descartó por inaplicable al contexto de despliegue. Las partidas de Jugador vs. IA no tienen reloj; el reglamento de torneos de la IRF prohíbe las partidas humano vs. inhumano, lo que hace estructuralmente imposibles las partidas competitivas cronometradas contra el motor; y el modo IA vs. IA no está implementado. La búsqueda a profundidad fija es por tanto apropiada tal como se diseñó, y de hecho es preferible en un contexto de entrenamiento porque ofrece una calidad constante en lugar de cortes de profundidad condicionados por el tiempo transcurrido.

▶ En curso
7.1 — Las Ponderaciones Heurísticas No Están Ajustadas

Los siete componentes heurísticos y sus ponderaciones se derivaron de primeros principios y razonamiento geométrico, no del ajuste empírico contra un corpus de partidas de Raumschach. La ponderación relativa de, por ejemplo, la bonificación de paridad del unicornio (+35 cp) frente a la penalización de seguridad del rey por rayo abierto (−20 cp por rayo) no ha sido validada contra resultados reales de partidas. La IRF está abordando esto actualmente con un procedimiento de optimización bayesiana aplicado a un corpus de autopartidas: los vectores de ponderación candidatos se evalúan contra las puntuaciones de búsqueda de quietud, y un optimizador bayesiano (usando un proceso gaussiano sustituto) propone actualizaciones sucesivas de las ponderaciones para minimizar el error de predicción. Es probable que esto revele que algunas ponderaciones están sustancialmente mal calibradas respecto a su derivación de primeros principios; las ponderaciones actualizadas se incorporarán al motor a medida que concluya el ajuste.

7.2 — Sin Libro de Aperturas

El motor juega la apertura desde cero en cada partida, sin una biblioteca de secuencias de apertura sólidas. Esto hace que su juego inicial sea estructuralmente inconsistente: el motor puede identificar correctamente en términos abstractos el valor del control del centro y la activación de piezas, pero no reproducirá líneas de apertura específicas y bien estudiadas que podrían emplear jugadores experimentados. Un modesto libro de aperturas de 10–15 movimientos por variante principal mejoraría sustancialmente la calidad y consistencia del juego inicial.

8. Hoja de Ruta de Desarrollo

Los siguientes elementos constituyen la agenda conocida para la Versión 2. Los elementos se identifican por etiquetas descriptivas en lugar de números de prioridad, que se vuelven ambiguos en las revisiones de la hoja de ruta.

▶ En curso
Generación del Corpus de Autopartidas

Se está generando un corpus de autopartidas — instancias de Raumfischer a profundidad 3 jugando ambos bandos — como requisito previo para el ajuste empírico de ponderaciones. El corpus cumple dos funciones: proporciona la señal de entrenamiento para el optimizador de ponderaciones (puntuaciones de búsqueda de quietud como objetivos, evaluación estática como predicción), y con el tiempo puede servir de semilla para un modesto libro de aperturas registrando las secuencias de movimientos iniciales más frecuentemente jugadas y más exitosas. El tamaño del corpus es la restricción determinante para la calidad del ajuste; la generación continúa en curso.

▶ En curso
Ajuste Empírico de Ponderaciones

El IRF aplica Bayes variacional de campo medio con aumentación de Pólya-Gamma para ajustar 26 parámetros de evaluación — cinco valores de pieza (dama, torre, alfil, unicornio y caballo, con el peón fijo en 100 cp), cinco bonificaciones de centralidad, dos términos de movilidad, dos bonificaciones de espacio, dos términos de seguridad del rey, cuatro términos de estructura de peones, cuatro bonificaciones de torre y dos términos de paridad de unicornio — con el fin de minimizar el error cuadrático medio de la evaluación estática respecto a la puntuación de búsqueda de quietud en el corpus de autopartidas. El Bayes variacional, cuyas actualizaciones de coordenadas son exactas y en forma cerrada bajo la aumentación de Pólya-Gamma, converge de manera rápida y consistente. Los parámetros calibrados se incorporarán al motor al concluir la optimización y se espera que mejoren sustancialmente la calibración cuantitativa, en particular los términos de paridad de unicornio y de seguridad del rey, cuya derivación desde primeros principios es más incierta.

Libro de Aperturas

Una biblioteca de secuencias de apertura sólidas, derivada del corpus de autopartidas y de partidas humanas anotadas cuando estén disponibles, reemplazaría la práctica actual del motor de evaluar la apertura desde cero en cada partida. Un libro de 10–15 movimientos por variante principal es un objetivo inicial realista. La construcción del libro depende de la disponibilidad del corpus; se aplaza hasta que el corpus sea suficientemente amplio como para producir recuentos de frecuencia estadísticamente significativos.

9. Conclusión

Raumfischer Versión 1 representa una extensión fundamentada del marco de búsqueda de Maackesgeist en la dirección de la sofisticación posicional. La infraestructura de búsqueda central — make/unmake en el lugar sin asignación de memoria, hash de Zobrist incremental, profundización iterativa con ventanas de aspiración, poda de movimiento nulo, búsqueda de quietud y ordenación de movimientos por asesinos e historial — se conserva sin modificaciones de Maackesgeist, habiendo sido validada allí y sin requerir revisión. La contribución original de Raumfischer es principalmente evaluativa: la evaluación Fischer de siete componentes reemplaza una función compacta de tres términos por una que reconoce la seguridad del rey, la estructura de peones, la activación de torres y el fenómeno geométricamente único del confinamiento de paridad del unicornio.

Se incorporaron siete mejoras de búsqueda durante el desarrollo de la Versión 1 y se documentan en el §5 junto a la arquitectura original. La movilidad en caché elimina el coste dominante por evaluación de la heurística de movilidad actualizando mobCache de forma incremental en cada make y unmake en lugar de regenerar los recuentos de movimientos pseudolegales desde cero en cada hoja. La Búsqueda de la Variación Principal confina las búsquedas de ventana completa al primer movimiento raíz y a los casos en que un movimiento posterior falla alto en un sondeo de ventana nula; dada la sólida ordenación de movimientos de Raumfischer, los sondeos de ventana nula confirman la mayoría de movimientos rápidamente y las búsquedas de ventana completa que habrían sido necesarias en el alfa-beta puro simplemente no se producen. Las Reducciones de Movimientos Tardíos amplían la profundidad de búsqueda efectiva reduciendo la exploración de movimientos al final de la lista ordenada, con una nueva búsqueda a profundidad completa activada solo cuando el resultado reducido eleva alfa. La poda de futilidad proporciona ahorros complementarios en profundidades superficiales, donde las LMR no se aplican. Las extensiones de jaque en la búsqueda de quietud previenen la mala evaluación de secuencias forzadas que avanzan mediante jaques en lugar de capturas. La poda delta elimina las capturas en la búsqueda de quietud que no pueden mejorar la puntuación ni en el mejor caso. La emulación de claves Zobrist de 64 bits reduce la probabilidad de colisión de hash a niveles despreciables. En conjunto, estas mejoras aumentan sustancialmente la velocidad y precisión de búsqueda sin coste para la corrección, y la tabla de transposición se reinicia adicionalmente entre partidas para prevenir la contaminación entre partidas.

La agenda de desarrollo restante es triple: completar el corpus de autopartidas, aplicar la optimización bayesiana para calibrar empíricamente las ponderaciones heurísticas, y construir un modesto libro de aperturas a partir de los registros de partidas resultantes.

La apuesta más profunda detrás de Raumfischer es que el estilo de juego de Fischer no era producto de la geometría del ajedrez bidimensional sino de una inteligencia espacial general — una preferencia por las piezas activas, las casillas dominantes, la disciplina estructural y la conversión implacable de ventajas — que se traslada intacta a tres dimensiones. Si el motor juega como Fischer habría jugado al Raumschach es una pregunta que solo se resolverá empíricamente, una partida a la vez.

Referencias

Dickins, A. S. M. (1971). A Guide to Fairy Chess. New York: Dover Publications.

Maack, F. (1907). Das Schachraumspiel: Dreidimensionales Schachspiel. Hamburg.

Shannon, C. E. (1950). Programming a computer for playing chess. Philosophical Magazine, 41(314), 256–275.

Zobrist, A. L. (1970). A new hashing method with application for game playing. Technical Report 88, University of Wisconsin.

Federación Internacional de Raumschach. (2026). Maackesgeist: A Raumschach Engine (Version 2). raumschach.org/maackesgeist.html

Código fuente: raumschach.html (2026). Federación Internacional de Raumschach. raumschach.org/raumschach.html