# Modelo mental

> Instantánea, comando, recibo, evento, tick y lote. Con estos seis conceptos entenderás por qué la API tiene esta forma y cómo escribir código rápido.

Fuente: https://war3ai.com/es/docs/concepts/

## Instantánea: leer, sin esperas

Cada **50 ms**, el runtime recorre el mundo entero en el hilo del juego y lo escribe en memoria compartida. Lo que devuelve `g.snapshot()` es un mundo **completo y coherente**:

- 16 slots de jugador: oro, madera, comida, límite de comida, total recolectado, raza;
- Hasta 1024 unidades: tipo, dueño, coordenadas, vida / maná (con sus máximos), orden actual y objetivo de la orden, **a quién ataca realmente** (objetivo de tarea), nivel / experiencia / puntos de habilidad del héroe, y quién puede verla;
- Hasta 256 detalles de unidad: 12 habilidades (nivel, enfriamiento restante), 8 buffs, 6 casillas de inventario;
- Objetos en el suelo, árboles (se refrescan cada 2 segundos), tabla de producción (progreso de entrenamientos / investigaciones / construcciones / mejoras), reloj del juego y hora del día dentro del juego.

Leer una copia cuesta unos **0.4 ms** (parseo en Python) y no espera al hilo del juego. Así que: **lee todo lo que quieras**. Interfaces como `g.units()`, `g.my_army()`, `g.cooldown()` o `g.inventory()` salen todas de la misma instantánea; llamarlas muchas veces en un mismo tick no cuesta casi nada.

> **Consejo**
>
> El periodo de publicación es configurable: `g.set_publish_period(ms)`, de 16 a 1000 milisegundos. Cada recopilación cuesta unos 0.5 ~ 0.9 ms en el hilo del juego, así que 33 ms no es problema. El valor es único para toda la máquina y gana el último que se escribe.

## Comando: escribir, en torno a un frame

`g.move / attack / gather / build / train / cast …` se entregan al hilo del juego para que los ejecute. El runtime ejecuta por lotes los comandos enviados por los clientes dentro del **despacho de eventos** del hilo del juego, así que un comando espera alrededor de **un frame** (unos 0.1 ms si cae en una ráfaga de eventos; si no, espera al siguiente despacho).

- Un comando acepta **una unidad o una lista**; las unidades de la lista reciben la orden juntas en el mismo frame;
- `queue='after'` equivale a encolar con Shift: primero termina lo que está haciendo y luego hace esto;
- En los comandos basta con pasar los objetos de unidad obtenidos de la instantánea; el SDK comprueba su identidad con el **par de handles** (las unidades nuevas reutilizan direcciones; los handles no se reutilizan).

## Recibo: todos los comandos tienen uno

```python
r = g.build(worker, "hbar", x, y)
if r:                      # el motor lo aceptó
    ...
else:
    r.reason               # 'rejected（金不够）' = "falta oro"
    r.verdict              # 8
r.exec_us                  # microsegundos que tardó en ejecutarse en el hilo del juego
```

El recibo se lee en el **mismo frame**: la orden de la unidad antes y después de darla, el valor devuelto por la función del motor y el código de motivo de la comprobación de viabilidad. Responde a «¿aceptó el motor este comando y, si no, por qué?», pero **no responde** a «¿se consiguió al final?». Para eso, mira la instantánea y los eventos.

Todos los códigos de estado y de motivo están en [Recibos y códigos de motivo](https://war3ai.com/es/docs/reason-codes/).

## Evento: qué ha pasado

`on_event(g, ev)` se ejecuta antes de cada `on_tick` y te entrega, uno a uno, los eventos ocurridos desde el tick anterior:

| Evento | Significado |
|---|---|
| `unit.appeared` / `unit.died` / `unit.removed` | Una unidad aparece, muere o desaparece (entrar en una mina de oro, ser convertida o que se pudra el cadáver también cuentan como desaparecer; no equivale a morir) |
| `unit.damaged` / `order.changed` / `owner.changed` | Pierde vida, cambia de orden, cambia de dueño |
| `hero.levelup` | Un héroe sube de nivel |
| `item.appeared` / `item.removed` | Un objeto aparece en el suelo, o alguien lo recoge o lo usa |
| `damage` | A nivel de motor: **cada golpe** de daño. Unidad de origen, tipo de ataque, tipo de daño, vida perdida real, daño antes de armadura |
| `killed` | A nivel de motor: este golpe la mató; incluye al asesino |
| `production.done` | Terminó un entrenamiento / investigación / construcción / mejora; incluye el código de cuatro caracteres y cuántos segundos de juego tardó. También llega el de los rivales |
| `spell.cast` | Una unidad lanzó una habilidad: código de cuatro caracteres de la habilidad, nivel, segundos de enfriamiento, punto de lanzamiento |
| `message` | Aparece una línea en un marco de mensajes de la pantalla: avisos del juego («Necesitas más granjas»), chat (`.chat` incluye quién habla y qué dice), mensajes del sistema |
| `selection.changed` / `player.left` | Cambió la selección del jugador local / un jugador se fue o fue eliminado por derrota |
| `game.started` / `game.ended` | Empieza una partida nueva / se sale de la partida |

Los eventos de entrada, como clics en botones del lienzo, atajos de teclado o clics en el suelo, están en [Interfaz y entrada](https://war3ai.com/es/docs/ui-input/).

> **Atención**
>
> El flujo de eventos es **global**: incluye las producciones terminadas del rival y las muertes de creeps. Filtra por `ev.owner` o por el handle de la unidad.

## Tick: el ritmo del Bot

Por defecto, `on_tick` se llama 5 veces por segundo (reloj real). Lo que tarda un tick es básicamente tu propio cálculo: la instantánea no espera y un comando tarda alrededor de un frame. Si un tick se pasa del periodo, el siguiente se aplaza automáticamente; no se acumulan.

- **A velocidad 2×, no esperes según el reloj real.** Para esperar 3 segundos de juego, comprueba que `g.clock()` haya avanzado 3; no uses `sleep(1.5)`.
- **No uses `sleep` dentro de `on_tick`.** Si necesitas «hacerlo dentro de un rato», apunta la hora de juego actual y compruébala en el siguiente tick.

## Lote: decenas de comandos, una sola espera

Si en un tick tienes que dar muchos comandos, envuélvelos en `with g.batch():`:

```python
with g.batch():
    g.attack(melee, target_a)
    g.attack(ranged, target_b)
    g.move(wounded, home.x, home.y)
    g.cast(hero, "thunderclap")
# al cerrar el bloque se envía todo el lote: se ejecuta en el mismo frame y se espera una sola vez al hilo del juego
```

- Dentro del bloque, los comandos devuelven `Pending`, que se convierte en recibo al cerrarse el bloque; leerlo antes lanza un error;
- Si dentro del bloque se lanza una excepción, **se descarta el lote entero** (medio lote de comandos es más peligroso que no enviar nada);
- Medido con 8 movimientos: uno a uno, 68 ~ 99 ms; en un lote, **6.5 ~ 10 ms**.

La misma idea sirve para las consultas: `g.can_do_many([(u, code), ...])` y `g.tech_many([...])` preguntan muchas cosas de una vez.

## Leer lo que acabas de escribir

En un mismo tick, la instantánea todavía no refleja el comando que acabas de dar (se pone al día en la siguiente publicación). Dos partes de tu lógica pueden pelearse por el mismo trabajador: una acaba de mandarlo a construir una granja y la otra, al mirar la instantánea, cree que sigue ocioso.

`g.order_of(u)` resuelve esto: hasta que la instantánea se pone al día, usa la orden nueva que indica el recibo. **Para saber si una unidad está ociosa, usa `g.order_of(u)`, no `u.order`.** `g.idle_workers()` ya excluye a los que acabas de mandar a trabajar en este tick.

## Niveles de latencia

| Nivel | Canal | Latencia | Para qué |
|---|---|---|---|
| 0 | Instantánea push + flujo de eventos | Leer una copia, unos 0.4 ms; datos nuevos cada 50 ms | Todas las interfaces de «observar» |
| 1 | Carril rápido | Alrededor de 1 frame; mediana de 0.06 ms con 6 procesos en paralelo | Todos los comandos y consultas (por defecto en el SDK) |
| 2 | Canal de control | 20 ~ 40 ms | Respaldo y unas pocas operaciones de interfaz (velocidad de juego, bocadillos, mensajes) |
| 3 | [Pasarela](https://war3ai.com/es/docs/gateway/) (WebSocket / JSON) | Nivel 1 + alrededor de 1 ms | Cualquier lenguaje, navegadores, LLM, programas en otra máquina |

En el [catálogo de la API](https://war3ai.com/es/api/), cada interfaz indica qué nivel usa.
