Metadata-Version: 2.4
Name: treta
Version: 0.3.0
Summary: IA adversaria online para videojuegos: aprende del jugador, contraataca, finta e improvisa siendo casi imposible de predecir. Cero dependencias.
Project-URL: Homepage, https://github.com/esraderey/treta
Project-URL: Changelog, https://github.com/esraderey/treta/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/esraderey/treta/issues
Author: esraderey
License: MIT
License-File: LICENSE
Keywords: game-ai,ia-adversaria,novelty-search,online-learning,opponent-modeling,regret-matching,videojuegos,zero-dependencies
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Games/Entertainment
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Requires-Python: >=3.10
Provides-Extra: test
Requires-Dist: hypothesis>=6; extra == 'test'
Requires-Dist: pytest>=8; extra == 'test'
Description-Content-Type: text/markdown

# treta

**IA adversaria online para videojuegos: aprende del jugador, contraataca, finta e improvisa — manteniéndose casi imposible de predecir.**

[![PyPI](https://img.shields.io/pypi/v/treta)](https://pypi.org/project/treta/)
![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)
![Dependencias: 0](https://img.shields.io/badge/dependencias-0-brightgreen)
![Licencia MIT](https://img.shields.io/badge/licencia-MIT-green)

Python puro, **cero dependencias**, determinista por semilla, ~O(|A|²) por turno (apta para 60 Hz).

*Registro de experimento: **EXP-008** (línea esraderey de microlibrerías zero-dep).*

```bash
pip install treta
```

## La idea en una frase

Los enemigos de videojuego fallan por dos extremos: los guionizados se vuelven **legibles** (el jugador los "resuelve") y los aleatorios se vuelven **incompetentes**. `treta` resuelve el dilema tratando la impredecibilidad como una **cantidad medida y optimizada**: el agente entrena un **autoadversario** —un predictor de su propia conducta, de la misma clase que usaría un jugador hábil— y castiga en su función de valor las acciones que ese observador anticiparía. Explota al rival *exactamente hasta donde puede sin volverse legible*.

## Uso

```python
from treta import Agente

ACCIONES = ["piedra", "papel", "tijera"]
VENCE = {"piedra": "tijera", "papel": "piedra", "tijera": "papel"}

# pago(a_ia, a_jugador) -> float (opcional; si falta, se estima de las recompensas)
def pago(a, j):
    return 0.0 if a == j else (1.0 if VENCE[a] == j else -1.0)

ia = Agente(ACCIONES, pago=pago, semilla=42)

# bucle del juego (movimientos simultáneos o por turnos):
accion = ia.decidir()               # acción del enemigo este turno
ia.observar(accion_del_jugador)     # cierra el turno; aprende online
                                    # (o ia.observar(j, recompensa=r) sin matriz de pagos)

ia.predictibilidad()   # [0,1] cuán legible es la IA para un observador competente
ia.certeza_jugador()   # [0,1] cuán bien modela al jugador
ia.improvisando()      # True durante un gambito
ia.fintando()          # True durante una finta (sembrando o cobrando un cebo)
ia.instantanea()       # telemetría para HUD/depuración
```

**Olvido explícito** (0.3.0): para eventos que el juego conoce y la IA no (nueva
partida, cambio de mapa, rival sustituido), `olvidar()` descuenta memoria por
canales y con dosis:

```python
ia.olvidar(predictores=0.5,   # fracción de evidencia conductual a olvidar [0, 1]
           pagos=0.7,         # fracción de la matriz de pagos aprendida
           regret=0.5,        # fracción del arrepentimiento acumulado
           recompensas=True,  # medias de recompensa, freno y estancamiento
           historial=True,    # historia reciente (contextos y red)
           confianza=True)    # certeza del jugador, predictibilidad y presión
```

Las fracciones intermedias conservan las medias y descuentan su peso: el agente
no cambia de opinión, pero vuelve a ser persuadible. `1.0` borra (los pagos
vuelven al prior; el regret, al uniforme; los n-gramas se vacían). Con los
valores por defecto es un no-op exacto, y no puede llamarse con un turno abierto.

**Alfabetos separados** (0.3.0): si la IA y el jugador no comparten acciones (la
IA produce unidades y el jugador elige rutas; un jefe con ataques propios),
`acciones_jugador` separa los conjuntos — `decidir()` devuelve etiquetas propias,
`observar()` espera las del jugador y `pago(a_propia, a_jugador)` recibe cada una
en su dominio:

```python
ia = Agente(["agresiva", "defensiva"],
            acciones_jugador=["ruta_norte", "ruta_centro", "ruta_sur"],
            semilla=42)
```

Omitido, ambos lados comparten `acciones` y el agente es bit-idéntico al de siempre.

**Memoria de valores** (`olvido_pagos`, 0.3.0): los pagos aprendidos son medias
permanentes — tras cien recompensas de +1, veinte de −1 apenas las mueven
(quedan en +0.667). El detector de deriva ya descuenta la evidencia ante un
cambio brusco; para entornos que derivan *despacio* (bajo su umbral),
`olvido_pagos` descuenta esa fracción de la evidencia de pagos con cada
observación registrada — una media exponencial de memoria ~1/f: `0.05` recuerda
~20 observaciones. El defecto `0.0` no toca nada (bit-idéntico); en el turno en que la alarma del
detector dispara, su descuento calibrado sustituye al continuo (no componen). Y
ojo con las dosis diminutas (~`0.02`): pueden rendir peor que nada, porque siguen
la deriva lo justo para callar la alarma sin sustituir su descuento.

**Matriz y recompensas** (`modo_pago`): si pasas `pago=` *y* recompensas reales en `observar()`, el defecto `"hibrido"` trata la matriz como un prior por celda (`prior_matriz` pseudo-observaciones) que las recompensas superan al acumularse — si la matriz jura que A es correcta pero el entorno paga B, el agente cruza a B. `"matriz"` restaura la semántica 0.1.x (la matriz manda; las recompensas solo mueven telemetría y fintas). `"aprendido"` aprende los valores solo de lo observado: la matriz no pesa como prior, pero cuando un turno no trae recompensa hace de oráculo y esa observación se registra — sin recompensas externas nunca, acabará aprendiendo la matriz celda a celda. Sin recompensas externas, híbrido y matriz son bit-idénticos.

`decidir(extra=...)` acepta cualquier rasgo observable hasheable (fase del nivel, distancia discretizada, arma equipada). Condiciona los modelos de contexto y, desde 0.2.0, también los **valores**: cada valor distinto de `extra` abre un bucket con su propia matriz de pagos estimada y su propio RM+, encogidos hacia los globales con `prior_estado` pseudo-observaciones (un estado recién visto hereda la conducta global y gana identidad con los datos). `max_estados` acota la tabla: los estados excedentes caen al global y `0` desactiva el mecanismo. Dos avisos: en `modo_pago="matriz"` la matriz es la autoridad sobre utilidades y solo el arrepentimiento se condiciona por estado — en híbrido (defecto) y aprendido los buckets condicionan también los pagos — en híbrido con jerarquía estado → global → matriz; en aprendido el escalón final es la media global aprendida, no la matriz —; y `extra` es un bucket discreto, no un vector de rasgos: discretiza grueso (mapa, fase, banda de economía), no pases valores casi-únicos.

## Siete mecanismos, un agente

| Mecanismo | Qué aporta | Base |
|---|---|---|
| **Modelo del jugador**: mezcla bayesiana de expertos n-grama (órdenes 0..k, olvido perezoso) + red neuronal pura (`RedMicro`, MLP online de entrada dispersa) | Predice la siguiente acción del jugador; los n-gramas memorizan patrones exactos, la red generaliza | PPM/CTW (Cleary & Witten 1984; Willems 1995), mezcla bayesiana con participación fija |
| **Autoadversario** *(la innovación de EXP-008)* | El mismo aparato apuntado a la propia IA. Su acierto define `predictibilidad`, que entra como **castigo** en el valor esperado: `EV'(a) = EV(a) − λ·presión·p_yo(a)`. La IA evita literalmente lo que un observador esperaría de ella, en proporción a cuánto la están leyendo | Emparentado con *Safe Opponent Exploitation* (Ganzfried & Sandholm 2012), resuelto aquí como regularizador online barato |
| **Fintas de segunda intención** | El autoadversario como sistema de puntería: ante un rival ilegible que lee y contraataca, siembra un cebo, verifica por *acuerdo* que el rival responde a la imagen propia (su jugada prevista es la mejor respuesta a `p_yo`) y cobra la contra-contra mientras el acuerdo dure. Sangría de siembra acotada, salida al segundo cobro perdido, respiros anti-patrón y enfriamiento exponencial ante sondeos fallidos | Enseñanza estratégica (Camerer, Ho & Chong 2002); la "segunda intención" de la esgrima |
| **Regret Matching+** | Piso teórico-de-juegos: contra un adversario que contraataca, empuja la política mixta hacia maximin | Hart & Mas-Colell 2000; Tammelin 2014 (RM+/CFR+) |
| **Improvisación por novedad** | Archivo acotado de *gambitos* (motivos de 2–4 acciones) con descriptor conductual; ante estancamiento o legibilidad alta ejecuta el motivo más novedoso (búsqueda de novedad + calidad, QD-lite) | Lehman & Stanley 2011; Mouret & Clune 2015 |
| **Detección de deriva en dos canales** | Page-Hinkley conductual (log-pérdida del modelo del jugador: si el jugador cambia de estilo, olvido de predictores) y, desde 0.3.0, Page-Hinkley de pago (error entre recompensa y estimación de valor, con unidad propia y tope de pico): si el entorno cambia lo que paga *sin que el rival cambie de conducta*, descuenta la evidencia de valor conservando las medias y readapta en ~10 turnos donde antes tardaba >130 | Page 1954 |
| **Valores por estado** *(0.2.0)* | La misma acción puede ganar en un mapa y perder en otro: pagos estimados y política RM+ por bucket de `extra`, encogidos al global por peso de datos. Sin `extra`, el agente es bit-idéntico a 0.1.0 | Partial pooling jerárquico (empírico-Bayes); bandit contextual de brazos discretos |

La **presión** modula el castigo: contra un rival estático (que no explota los patrones de la IA) el impuesto de impredecibilidad es mínimo y la explotación es agresiva; contra un rival que lee y contraataca, el castigo, la temperatura y la mezcla con RM+ suben solos.

## Banco adversario (reproducible: `python ejemplos/duelo.py`)

Piedra-papel-tijera de suma cero (azar = pago 0, predicción por azar = 33.3 %), 4000 rondas, semilla 7. El *observador externo* es una mezcla de n-gramas (órdenes 0–3) que intenta predecir a la IA — el mismo modelo que usaría un jugador metódico.

| Rival | Pago medio IA (últ. mitad) | Acierto del observador sobre la IA |
|---|---|---|
| Repetidor (acción fija) | **+0.868** | 88.7 % |
| Ciclador (periodo 4) | **+0.712** | 78.4 % |
| Imitador (copia a la IA) | **+0.774** | 79.2 % |
| Aleatorio uniforme | +0.036 | 35.0 % |
| **Contra-predictor** (predice a la IA con sus mismos modelos y contraataca) | **+0.217** | **25.5 %** *(muy bajo el azar)* |
| Cambiante (deriva en t=1200; últ. 400) | **+0.765** | 78.0 % |

El banco incluye además un duelo con **pagos dependientes del estado** (la misma acción gana en el estado A y pierde en el B, con jugada rival constante): con valores por estado la IA cobra +0.89..+0.94 donde la versión global queda clavada en ~0.00 (semillas 7/11/23; `tests/test_adversarios.py::test_condiciona_valores_por_estado`).

Y un duelo de **deriva de recompensa** (el rival repite siempre la misma acción; lo que cambia es qué acción paga): el canal conductual es ciego ahí por construcción — en 0.2.0 la readaptación tardaba 134..218 turnos con el contador de derivas en cero. El canal de pago la detecta con exactamente una alarma y readapta en 8..13 turnos (semillas 7/11/23; `test_detecta_deriva_de_recompensa`), sin una sola falsa alarma en los duelos estacionarios, por estado (también con visitas desiguales entre estados) y de matriz-contra-entorno del banco, y a lo sumo una aislada bajo recompensa ruidosa (σ=0.3, 2000 turnos).

Desde 0.3.0 el banco añade un duelo de **deriva gradual** (la recompensa se desliza bajo el umbral del detector: la media permanente arrastra un sesgo que `olvido_pagos=0.05` drena, recortando la pérdida post-cruce de ~0.12 a ~0.08 por turno; `test_olvido_continuo_drena_la_deriva_gradual`) y un duelo **asimétrico** (3 doctrinas propias contra 2 rutas del jugador cicladas: +0.72..+0.80 donde la mejor respuesta ciega renta +0.5; `test_asimetrico_explota_rutas`).

Lectura: explota con fuerza todo patrón (pago cercano a +1 donde lo hay) y queda neutral ante el azar puro. El acierto alto del observador contra rivales estáticos **no es un fallo**: nadie castiga ahí la legibilidad, así que la `presión` es ~0 y el agente elige explotación máxima. Contra el único rival que sí lee y contraataca, la IA **gana con claridad** y su acción cae *muy por debajo del azar* para el observador: el castigo por auto-predictibilidad la aleja de lo esperado y las fintas cobran, además, la imagen que el rival se forma de ella (robusto en semillas 7/11/23: pago +0.217..+0.239, acierto 23.8..25.5 %). Contra el Cambiante, Page-Hinkley detecta la deriva (1 alarma) y el pago se recupera a +0.765 en 400 rondas.

## Garantías y límites (honestos)

- **Garantiza**: determinismo por semilla; política válida en todo paso; coste por turno O(k + |A|²) sin asignaciones grandes; con matriz de pagos exacta (`modo_pago="matriz"`, o híbrido sin recompensas externas), RM+ acumula arrepentimientos exactos que empujan la mezcla hacia maximin — la garantía formal de arrepentimiento sublineal aplica a RM+ jugado en solitario, no a la política mixta final (además, los turnos de gambito no actualizan el regret): el respaldo de la no-explotabilidad es el banco empírico.
- **No hace**: espacios de acción continuos, percepción (píxeles), planificación a varios turnos ni aprendizaje entre partidas persistido (serializa tú `instantanea` + estado si lo necesitas). El "castigo por auto-predictibilidad" es un regularizador empírico, no un equilibrio exacto: el banco de arriba es su evidencia.
- Alfabetos de 2 a 32 acciones por lado (propias y del jugador, compartidos o separados con `acciones_jugador`). Para juegos con estado rico, discretiza lo observable y pásalo por `extra`: n-gramas y valores por estado lo condicionan todo salvo la red neuronal interna, que solo ve las últimas jugadas (con muchos estados casi-únicos, los buckets se fragmentan y el encogimiento los devuelve al global).
- Las fintas asumen rival adversario (aprox. suma cero: su mejor respuesta minimiza tu pago). En juegos sin ese antagonismo la compuerta de margen las deja inertes.

## Desarrollo

```bash
pip install -e ".[test]"
pytest            # unidades + propiedades (Hypothesis) + banco adversario
```

Historial de cambios: [CHANGELOG.md](https://github.com/esraderey/treta/blob/main/CHANGELOG.md).

## Licencia

MIT © esraderey
