feat: 1v9 convertite en leyenda LoL career simulator

This commit is contained in:
Renato
2026-08-03 05:54:46 +02:00
commit 472a250782
75 changed files with 2464 additions and 0 deletions
+122
View File
@@ -0,0 +1,122 @@
# Arquitectura — 1v9: Convertite en Leyenda
## Visión general
Es un **juego de decisiones** (historia interactiva) en un **único archivo** `index.html`
(HTML + CSS + JS inline, sin dependencias externas para jugar). Todo el estado vive en memoria
durante la partida; no hay persistencia obligatoria (un `localStorage` opcional guarda el
"Salón de la fama" con las mejores carreras).
```
Landing → Creación → Oferta de academia → Loop de carrera (N splits) → Resumen final (compartible)
```
---
## Flujo de pantallas
| Pantalla | Rol |
|----------|-----|
| **Landing** | Presentación + botón "Convertite en leyenda" + Salón de la fama |
| **Creación** | Nick, nº de invocador, mano, región, rol |
| **Oferta de academia** | 3 equipos aleatorios de tu liga para elegir |
| **Carrera (Screen A)** | Panel de jugador + decisión + timeline, 2 columnas |
| **Resumen (Screen B)** | 3 paneles + grid de orgs + botón "Jugar de nuevo" |
---
## Datos del juego
### REGIONES
`LCK / LPL / LEC / LCS / LTA / VCS`, cada una con:
- `code` (KR/CN/EU/NA/AM/VN), `flag`, `country`, `flavor`.
- `threshold`: umbral de ELO para competir en su primera división (LCK=68…VCS=50).
### TEAMS (68 equipos reales)
`{ id, name, div }``div: 1` = primera división, `div: 2` = segunda/academia.
Los logos se resuelven con `LOGO_EXT[id]``logos/<id>.<ext>`.
### ROLES
`{ em, fl, kill, ast, death, pentaW }` — coeficientes de farmeo/perf de cada rol.
### EVENTS (26 eventos de decisión)
Cada evento: `{ cat, title, ctx, opts[] }`.
Cada opción: `{ t, d, risk, pos, neg, x? }`
- `d`: deltas a stats (`mecanica, vision, farmeo, sinergia, marca`).
- `risk`: probabilidad de fracaso (0-1).
- `pos`/`neg`: textos de resultado (éxito/fracaso).
- `x`: efectos extra (`rolChange`, `transfer`).
### COMPETITIONS (5 internacionales)
`{ split, ic, flavor }` — First Stand (2), MSI (4), EWC (6), Asian Games (8), Worlds (último).
---
## Estado de partida (`G`)
```js
{
nick, num, hand, region, role,
teamId, div,
stats: { mecanica, vision, farmeo, sinergia, marca },
split, totalSplits,
kills, assists, deaths, games, penta,
value, maxValue, salary, ratingMax,
teamTitles, indTitles, intl,
teams, events, history,
regSelect: { games, kills, ast, apps },
retired, retiring, finalRating
}
```
---
## Mecánicas clave
### Rating / ELO
Media ponderada de las 5 stats (mecánica 1.15, farmeo 1.05, sinergia 1.0, visión 0.95, marca 0.85),
clamp `[40, 99]`.
### Market value
`50000 * 10^((rating-40)*0.055)` × bonus por títulos, redondeado a `10k`.
### Crecimiento pasivo
Cada split suma `+0.48 + rand*0.2` (+ bonus por winRate) a cada stat; rendimiento malo reduce.
### Decisiones
`ok = Math.random() >= (risk + 0.03)`. Éxito: `+delta*2.6`. Fracaso: `-delta*2.6*0.9`.
### Fichajes
Solo desde **split 4**, con ~22% de chance por split y `rating >= threshold-4`.
Ofertas de otros equipos (misma región o extranjeros de primera si el rating alcanza).
### Competencias internacionales
**Solo equipos de primera división** (un equipo de 2ª no va a MSI/Worlds). Bracket de 4 rondas
con probabilidad por rating: grupos → cuartos → semis → final.
### Títulos
- **Regional**: cada split par, si `rating >= threshold-X` (primera o segunda división).
- **Individuales**: Guante de Oro (ELO≥62), Rey del Farmeo (farmeo≥60), Carry Definitivo (penta≥1), Community Darling (marca≥58), MVP del Split.
---
## UI (estilo Copero)
Design tokens en `:root`:
```
--bg-base:#0a0a0c --bg-card:#18181b --bg-card-alt:#1f1f23 --bg-row-active:#0e3a3f
--accent-ovr:#d9730a --accent-role:#7c3aed
--success:#22c55e --danger:#ef4444 --border-subtle:#27272a
```
- **Screen A**: 2 columnas (3fr/2fr). Izquierda: badge ELO + panel jugador + stats + logros + decisión.
Derecha: timeline de carrera (SPLIT/ORG/ELO/GAM/KIL/AST) con fila activa teal y filas futuras grises.
- **Screen B**: 3 paneles (identidad, selección regional, premios) + grid de orgs + botón "Jugar de nuevo".
- **Responsive**: <768px colapsa la timeline (default cerrada) y apila a 1 columna.
---
## Export de imagen
Un `<canvas>` 1080×1920 dibuja el resumen completo (dark bg, badges ELO, stats, títulos, orgs)
y se descarga como PNG. Es el mecanismo de viralidad del juego.
+100
View File
@@ -0,0 +1,100 @@
# Deploy — lolero.cbcren.online
El sitio corre en un servidor estático (`python3 -m http.server`) en el puerto **8899**,
protegido por un reverse proxy **Caddy** con TLS automático.
```
Internet → Caddy (80/443, TLS automático)
└─ lolero.cbcren.online → reverse_proxy 172.20.0.1:8899
└─ python3 -m http.server (systemd: lolero.service)
├─ index.html
└─ logos/ (68 archivos)
```
---
## 1. Servir los archivos (systemd)
```ini
# /etc/systemd/system/lolero.service
[Unit]
Description=1v9 Convertite en Leyenda - static site
After=network.target
[Service]
User=ren
WorkingDirectory=/home/ren/lolero
ExecStart=/usr/bin/python3 -m http.server 8899 --bind 0.0.0.0
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
```
```bash
sudo cp lolero.service /etc/systemd/system/lolero.service
sudo systemctl daemon-reload
sudo systemctl enable --now lolero
```
> **Nota:** `http.server` lee los archivos del disco en cada request, así que **no hace falta
> reiniciar el servicio** cuando se actualiza `index.html`. El cambio se sirve solo.
## 2. Caddy (reverse proxy)
El Caddyfile activo está en `/home/ren/immich/Caddyfile` (es un bind mount del contenedor
`caddy-ingress`, no `/etc/caddy/Caddyfile`). Se agregó:
```
lolero.cbcren.online {
reverse_proxy 172.20.0.1:8899
encode gzip
header {
X-Frame-Options SAMEORIGIN
X-Content-Type-Options nosniff
}
log {
output file /data/caddy/lolero.cbcren.online.log
}
}
```
Validar y recargar:
```bash
docker exec caddy-ingress caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
docker exec caddy-ingress caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile
```
> `172.20.0.1` es el gateway de la red docker `caddy` (donde corre `caddy-ingress`).
> Cualquier proxy hacia el host usa esa IP.
## 3. DNS
Registro A: `lolero.cbcren.online → 194.163.191.200` (ya creado). Caddy emite el certificado
Let's Encrypt automáticamente en el primer request.
## 4. Verificación
```bash
curl -sk -o /dev/null -w "%{http_code}\n" https://lolero.cbcren.online/ # 200
curl -sk -o /dev/null -w "%{http_code}\n" https://lolero.cbcren.online/logos/T1.png # 200
```
## 5. Actualizar contenido
Reemplazar `/home/ren/lolero/index.html` y/o `/home/ren/lolero/logos/`. No requiere restart.
## 6. Rollback
```bash
sudo systemctl stop lolero # baja el servicio
# el 503 de Caddy se devuelve mientras tanto; subirlo de nuevo con:
sudo systemctl start lolero
```
## Notas de puertos
El puerto **8899** se eligió libre (verificado con `ss -tln`). No se tocan puertos en uso
(8080, 8090, 3000, 8000, etc.).
+49
View File
@@ -0,0 +1,49 @@
# Próximos pasos — 1v9: Convertite en Leyenda
Estado actual y mejoras posibles, en orden de impacto.
## ✅ Hecho
- [x] Juego completo en un solo archivo (`index.html`), autocontenido, offline.
- [x] 68 equipos reales con logos, 5 roles, 6 regiones.
- [x] 26 eventos de decisión en 6 categorías.
- [x] 5 competencias internacionales (First Stand, MSI, EWC, Asian Games, Worlds).
- [x] Títulos regionales (1ª y 2ª) + premios individuales.
- [x] Fichajes con cadencia realista (≥ split 4, ~2/carrera).
- [x] Gating: equipos de 2ª división no van a internacionales.
- [x] UI estilo Copero (dark dashboard, badges, pills, timeline).
- [x] Responsive mobile/desktop, timeline colapsable.
- [x] Export de imagen 1080×1920 compartible.
- [x] Deploy en https://lolero.cbcren.online (Caddy + systemd + puerto 8899).
- [x] Documentación (README, ARCHITECTURE, TESTING, DEPLOY).
## 🔜 Ideas de mejora
### Gameplay
- [ ] Más eventos por categoría (replayability).
- [ ] Más premios individuales (Mejor Novato, MVP del Split con más variantes).
- [ ] Eventos de "momento" (ganar/revivir un BO5, finales emocionantes).
- [ ] Sistema de lesiones/visa más ligado a la selección regional.
### UI / UX
- [ ] Animaciones de transición entre pantallas.
- [ ] Sonido (efectos mínimos, sin dependencias externas).
- [ ] Soporte de idioma (EN) según el locale del navegador.
- [ ] Tema claro opcional.
### Datos
- [ ] Más equipos de segunda división por región.
- [ ] Verificar logos nuevos si cambian (los `_std` de Liquidpedia son pequeños).
### Distribución / viralidad
- [ ] PWA (manifest + service worker) para instalación y offline total.
- [ ] Open Graph / tarjetas de compartir (el export ya es la imagen).
- [ ] Analítica ligera (opcional, sin trackeo de datos personales).
### Infra
- [ ] HTTPS ya resuelto por Caddy. Considerar caché de logos con `Cache-Control` largo.
- [ ] Backup del repo en Gitea (este repo).
## Contribución
Ver `TESTING.md` para el workflow de verificación antes de mergear cambios.
+109
View File
@@ -0,0 +1,109 @@
# Testing — 1v9: Convertite en Leyenda
Este documento describe el workflow de testing que usamos para verificar el juego de punta a punta.
Es reproducible en cualquier máquina con Node.js ≥18 y Python 3 + Playwright.
---
## 1. Validación de sintaxis JS
Extraemos el `<script>` inline y lo chequeamos con Node (sin ejecutarlo):
```bash
python3 -c "import re; open('/tmp/game.js','w').write(re.search(r'<script>(.*?)</script>', open('index.html').read(), re.S).group(1))"
node --check /tmp/game.js && echo "JS OK"
```
Esto detecta errores de sintaxis antes de tocar el navegador.
---
## 2. Smoke test local (Playwright)
Levantamos un servidor estático y jugamos una carrera completa con un bot, detectando
errores de consola y fallos de red:
```bash
python3 -m http.server 8899 --bind 0.0.0.0 &
```
Un script Playwright (chromium headless) que:
1. Abre la página en viewport móvil (390×844).
2. Completa la creación de player.
3. Elige una oferta de academia.
4. Juega hasta el resumen (clic en opciones/continuar).
5. Captura **0 errores de consola**, **0 requests fallidos** y que el resumen se muestre.
```python
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page(viewport={"width":390,"height":844})
errs = []
page.on("pageerror", lambda e: errs.append(str(e)))
page.on("console", lambda m: errs.append(m.text) if m.type=="error" else None)
page.on("requestfailed", lambda r: errs.append(r.url))
await page.goto("http://127.0.0.1:8899/")
# ... click thru ...
print("ERRORS:", errs)
```
---
## 3. Layout check (sin lectura de imágenes)
Como el modelo no puede ver screenshots, verificamos el layout programáticamente:
- **Overflow horizontal**: `document.documentElement.scrollWidth > window.innerWidth` debe ser `false`.
- **Timeline colapsada en mobile**: el `.tl-body` debe tener la clase `closed` por default en viewport <768px.
- **Design tokens**: `getComputedStyle(document.documentElement)` debe devolver `--accent-ovr:#d9730a`, etc.
---
## 4. Verificación de mecánicas (tests dirigidos)
### Gating de competencias (1ª división)
Empezar en un equipo de **2ª división** y confirmar que NO aparecen eventos internacionales
hasta ascender a 1ª. Se rastrea el `dc-cat` de cada decisión contra el `G.div`.
### Cadencia de fichajes
Jugar y registrar en qué splits aparecen eventos "Fichaje". Debe ser **≥ split 4** y ~2 por carrera
(no en cada split).
### Diversidad de competencias
Jugar 10+ carreras y confirmar que aparecen las 5 (First Stand, MSI, EWC, Asian Games, Worlds).
### Stats / backend
Verificar que `G.stats` no tenga claves fantasma (`valor`) ni `NaN` al final de la carrera.
---
## 5. Export de imagen
Se fuerza el fin de carrera con datos de prueba, se clickea "Descargar imagen" y se valida el PNG con PIL:
```python
from PIL import Image
img = Image.open(path)
print(img.size, img.mode) # 1080x1920 RGBA
print(img.convert('RGB').getpixel((100,100))) # ~(10,10,12) dark bg
print(img.convert('L').getextrema()) # rango de luminancia (texto presente)
```
---
## 6. Test final contra producción (HTTPS)
Repetimos los smoke tests contra `https://lolero.cbcren.online` (no solo local), para validar
el deploy real (Caddy + TLS + serve de logos).
---
## Resultado esperado
- 0 errores de consola, 0 requests fallidos.
- Resumen alcanzado en 100% de las carreras.
- Sin overflow horizontal en desktop ni mobile.
- Export 1080×1920 PNG válido.
- Las 5 competencias aparecen; fichajes ≥ split 4; 2ª división no va a internacionales.