10 — Testing, experimentos, plan por fases y riesgos
1. Pirámide de tests
| Tipo |
Carpeta |
Qué cubre |
Herramientas |
| Unitarios |
tests/unit/ |
Cartas, evaluación de manos, legalidad, tanteo, transiciones |
pytest |
| Basados en propiedades |
tests/property/ |
Invariantes en miles de partidas aleatorias |
hypothesis |
| Escenarios |
tests/scenarios/ |
Manos y situaciones fijadas a mano ("deck amañado"), una por artículo FEM |
pytest + fixtures YAML |
| Integración |
tests/integration/ |
Partida completa vía play_game, runner, sandbox, API HTTP/WS |
pytest, httpx |
| Privacidad |
tests/privacy/ |
Fugas de información |
pytest + hypothesis |
| Conformidad de bots |
tests/bot_conformance/ |
Todo bot registrado |
pytest (parametrizado) |
| E2E web |
web/tests/ |
Partida completa en el navegador |
Playwright |
| Rendimiento |
tests/perf/ |
Manos/s del motor, latencia de los bots |
pytest-benchmark |
2. Casos clave del motor
- Baraja: 40 cartas únicas; 3 cuenta como rey y 2 como as en grande, chica, pares y juego.
- Reparto: 4 cartas, orden antihorario, mano a la derecha del dealer, rotación.
- Mus: orden FEM IV.4; mus de los cuatro → descartes 1-4; rebarajar el descarte (III.15); varias rondas de mus.
- Grande/chica: orden, empates a favor del mano, valor en paso.
- Pares: par/medias/duples, comparación, una sola pareja con pares (sin envites), nadie con pares.
- Juego/punto: orden 31 > 32 > 40 > 37…33; punto si nadie tiene juego; un solo jugador con juego.
- Envites: negada (1); subida rechazada; "si uno quiere, vale el quiero" (VI.4); deje; órdago en cada lance.
- Órdago: aceptado → fin del juego y anulación de envites anteriores (VII.12); rechazado → negada y la jugada sigue.
- "Quiero y no pierdo": el postre con 33 (VI.9-10).
- Tanteo: orden de lances (VII.2); llegar a 40 en grande antes que el rival en juego.
- Final: 40 exactos o más; "estamos dentro" a ≤ 4.
- Ilegales: fuera de turno, envido sin pares en pares, descartar 0 o 5, acción tras el final →
IllegalAction y estado intacto.
- Límite: mazo agotado en el descarte, cuatro rondas de mus seguidas, apuesta enorme aceptada (> tantos restantes).
- Para N = 5 000 partidas aleatorias y cada observación de cada asiento: el JSON no contiene
ninguna carta de otro asiento antes de
HandsRevealed.
- Ninguna observación contiene cartas del mazo ni del descarte ajeno.
- Un
SpyBot que intenta acceder al motor (por ejemplo gc.get_objects()) no encuentra
ningún GameState en su proceso de sandbox.
- Las respuestas HTTP/WS de
/games/{id} no contienen manos ajenas.
- Los logs fuera del canal
musst.secret no contienen cartas.
- El replay de una partida sin terminar está prohibido (403).
- Prueba de "observación mínima": dos estados que solo difieren en las cartas ocultas
producen observaciones idénticas para el asiento afectado.
4. Invariantes (hypothesis)
- En cualquier momento hay 40 cartas entre manos, mazo y descarte.
- El marcador nunca disminuye.
- La partida siempre termina (no hay ciclos infinitos de mus: tope configurable + rebarajado).
replay(seed, actions) reproduce el log byte a byte.
- Cada acción ejecutada pertenece a
legal_actions en ese estado.
5. Experimentos reproducibles (experiments/)
| Experimento |
Métrica |
| Escalera de niveles (Random < Sencillo < Medio < Pro) |
Win rate con IC al 95 % en mus duplicado, tantos medios |
| Básico vs adaptativo frente a perfiles sintéticos |
Mejora del win rate frente a farolero, conservador y cambiante |
| Calibración |
Brier score, curva de fiabilidad de P(ganar lance) y P(farol) |
| Adaptación |
Partidas hasta que el error del PlayerModel < 0.1; retraso en detectar el cambio de estrategia |
| Coste |
Tiempo de decisión p50/p99 por nivel |
| Legalidad |
% de decisiones legales (debe ser 100 %) |
Cada experimento se ejecuta con python -m experiments.<nombre> --seed 12345 y guarda
CSV + figura en experiments/results/.
6. CI (GitHub Actions)
| Workflow |
Disparador |
Pasos |
ci.yml |
push / PR a develop y main |
ruff (lint y format), mypy, pytest + cobertura, build del paquete, build del frontend |
bot-pr.yml |
PR que toca bots/** |
análisis estático, conformidad, mini-torneo contra los bots de referencia |
docs.yml |
PR |
mkdocs build --strict (ReadTheDocs publica) |
tournament.yml |
merge en main + nightly |
torneo + publicar scoreboard en Pages |
deploy.yml |
merge en main |
desplegar backend y frontend |
7. Plan por fases
He agrupado tus 31 fases en hitos, con dos cambios de orden justificados:
- CI/CD y estructura del repo pasan a la Fase 1 (no a la 27): la práctica puntúa C.3
("tests en cada PR") y C.2, y tenerlo desde el principio protege el motor.
- Bot básico, API de bots y una web mínima van antes que usuarios y perfiles: son el
contenido del punto de control (Secciones 1 y 2).
| Hito |
Fases |
Entregable |
Criterio de salida |
| H0 Validación |
0 |
Este diseño + respuestas del profesor |
Preguntas P1-P8 resueltas |
| H1 Cimientos |
1, (27) |
Repo: pyproject, LICENSE, README, main/develop protegidas, CI con ruff, mypy y pytest, MkDocs |
CI verde en un PR |
| H2 Motor |
2-10 |
musst.engine completo; play_game con RandomBot |
Todos los tests de reglas, invariantes y privacidad en verde; cobertura ≥ 90 % |
| H3 Plataforma |
11, 12 |
SimpleBot, API pública, sandbox, tests de conformidad, tutorial "crea tu bot" |
Un bot de ejemplo se añade sin tocar el motor |
| H4 Web MVP |
13 |
FastAPI + mesa jugable (humano + bot compañero vs 2 bots), desplegada |
URL pública, partida completa → punto de control |
| H5 Competición |
25, 26 |
Arena + scoreboard automático |
El torneo sobrevive a un bot roto inyectado |
| H6 IA |
16-20 |
Agentes, Orquestador, Juez, MediumBot, ProBot |
La escalera de niveles es significativa |
| H7 Aprendizaje |
14, 15, 21, 22 |
Usuarios, perfiles, PlayerModel, aprendizaje post-partida |
Los escenarios sintéticos se detectan |
| H8 Análisis |
23, 24 |
Replay, estadísticas, post-partida, contrafactual |
Replay idéntico al log |
| H9 Producto |
28-30 |
Tutorial, entrenamiento, docs finales, pulido, accesibilidad |
Lighthouse a11y ≥ 90 |
Cada fase se hace en su rama feature/, con un PR a develop y sus tests.
8. Posibles problemas y riesgos
| # |
Riesgo |
Impacto |
Mitigación |
| R1 |
El profesor no acepta el Mus de 4 jugadores |
Bloqueante |
P1 con alternativas preparadas; el motor sigue valiendo |
| R2 |
Valores de las jugadas no especificados por la FEM |
Medio |
[CONVENCIÓN] configurable + P5 |
| R3 |
Dos humanos en la misma pareja necesitan dos dispositivos (en uno se verían las cartas del compañero) |
Alto para el Modo 1 |
MVP: humano + bot compañero; después, sala con código de invitación por WebSocket. Nunca hot-seat sin pantalla de "pasa el dispositivo" |
| R4 |
Hosting gratuito con arranque en frío / disco efímero |
Medio |
Pantalla de "despertando servidor"; BD externa gratuita; Plan B con Streamlit |
| R5 |
Monte Carlo lento en Python |
Medio |
Numpy vectorizado, presupuesto de tiempo, precomputar tablas de manos (solo hay 40C4 = 91 390 manos) |
| R6 |
Alcance excesivo frente a la fecha de entrega |
Alto |
Los hitos H1-H5 son el camino crítico de la nota; el resto es incremental |
| R7 |
Sandbox de Python no es seguridad fuerte |
Medio |
Defensa por capas + revisión de PR (doc 05 §3), declarado honestamente |
| R8 |
El PlayerModel aprende poco con pocas partidas |
Medio |
Priors jerárquicos, confianza explícita, escenarios sintéticos para demostrarlo |
| R9 |
Fotos de usuarios (datos personales) |
Medio |
No guardar el original; avatar generado localmente o de un set; consentimiento |
| R10 |
Requisito de la práctica sobre IA de pago |
— |
Usar solo herramientas permitidas por la universidad (§5.8 de la práctica) |
| R11 |
Defensa: todos deben poder explicar todo |
Alto |
ADRs, diseño simple e interpretable, reparto de "propietarios" por módulo |