Documentación técnica viva del proyecto Games Bakuryu — Dominó MVP
Internet → Cloudflare
├── domino.bakuryu.com → Nginx → Godot Web Export
├── cdn.bakuryu.com → Worker → GCS edge
├── api.bakuryu.com → Nginx → Docker (8 servicios Go)
├── web.bakuryu.com → Landing page
└── d.bakuryu.com → Dev dashboard (este sitio)
| Servicio | REST | gRPC | Propósito |
|---|---|---|---|
| auth-service | 4001 | 5001 | Login, JWT, KYC |
| wallet-service | 4002 | 5002 | Billetera, ledger HMAC |
| subscription-service | 4003 | - | Free/Premium |
| social-service | 4004 | - | Amigos, chat |
| shop-service | 4005 | - | Cosméticos |
| ranking-service | 4006 | 5003 | ELO |
| audit-service | 4007 | 5004 | Auditoría |
| domino-service | 4010 | 5010 | Juego dominó |
curl -s4 -X POST https://api.bakuryu.com/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","username":"testuser","password":"SecurePass123!"}'
curl -s4 -X POST https://api.bakuryu.com/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"SecurePass123!","remember_me":true}'
curl -s4 https://api.bakuryu.com/auth/profile \ -H "Authorization: Bearer <JWT_TOKEN>"
curl -s4 https://api.bakuryu.com/api/lobby \ -H "Authorization: Bearer <JWT_TOKEN>"
curl -s4 -X POST https://api.bakuryu.com/api/games \
-H "Authorization: Bearer <JWT_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"mode":"fvd","max_players":4,"bet":0,"is_ranked":true}'
Worker: Cloudflare domino-cdn (cdn.bakuryu.com)
Bucket: gs://bakuryu/domino/ (1,187 assets)
Key GCP: /etc/gcp/gcp-key.json
Estructura:
cdn.bakuryu.com/
├── 2d/
│ ├── tiles/{classic,gold,jade,marble,obsidian,wood,flat}/tile_{X}_{Y}.png (28+1 c/u)
│ └── avatars/{avatar_blue,gold,green,red}.png
├── 3d/tiles/{classic,gold,jade,marble,obsidian,rosewood,amber}/tile_{X}_{Y}.glb (28+1 c/u)
├── environments/
│ ├── cantina_caracas/bg/scene_4k.png
│ ├── vip_club/bg/scene_4k.png
│ ├── playa_margarita/bg/scene_4k.png
│ ├── taberna_andalucia/bg/scene_4k.png
│ └── maracaibo/bg/scene_4k.png
├── audio/
│ ├── sfx/{13 sonidos}.mp3
│ ├── music/{7 tracks}.mp3
│ └── voices/{personaje}/{emocion}{##}.mp3 (416 archivos)
├── ui/{button_*,panel_*,icon_*,badge_*,reactions/*}.png
└── props/{candle_flicker,ceiling_fan,chandelier_sway,...}/*.png
Carga en Godot: environment_manager.gd usa CDN_BASE = https://cdn.bakuryu.com (sin /domino/ — el Worker lo agrega). tile_renderer.gd usa CDN_TILES con paths directos. Fallback procedural si CDN no responde.
| Opcode | Dirección | Descripción |
|---|---|---|
auth:login | → Servidor | Handshake con JWT |
auth:login_ack | ← Cliente | Confirmación + estado |
game:hand_deal | ← Cliente | Repartición de fichas |
game:state | ← Cliente | Estado del tablero |
play_tile | → Servidor | Jugar ficha {"ficha":[6,4],"extremo":"izq"} |
pass_turn | → Servidor | Pasar turno |
buy_tile | → Servidor | Comprar del pozo |
chat:msg | ↔ | Chat (max 500 chars) |
heartbeat | ↔ | Keep-alive 15s |
internal/ws/game_manager.go — bridge entre WS y engine. Métodos:
| Método | Descripción |
|---|---|
| CreateGame | Crea partida con creator como North |
| JoinGame | Asigna posición libre (East/South/West) |
| StartGame | Llena slots vacíos con bots, inicia Round |
| PlayTile | Valida + aplica jugada, log a DB, broadcast |
| PassTurn | Pasa turno, trigger bot automático |
| BuyTile | Compra del pozo, broadcast estado |
internal/ws/redis_store.go — snapshot JSON de ManagedGame.
Key: domino:game:{gameID} TTL: 72h
saveToRedisLocked() → tras cada broadcast
LoadAllActive() → restore al arrancar server
deleteFromRedisLocked() → al finalizar partida
internal/server/grpc.go vía proto/domino/domino.proto:
| Endpoint | Retorna |
|---|---|
| GetGameState | Estado de partida activa (score, ronda, variante) |
| GetGameResult | Resultado final de partida |
| ListActiveGames | Resumen de partidas activas |
| # | Error | Causa | Solución |
|---|---|---|---|
| 1.1 | Wrangler auth fail | IPv6 no whitelisted | Usar curl -4 para API |
| 1.2 | SyntaxError: export | ES module vs Service Worker | Usar addEventListener |
| 1.3 | nginx PID missing | 2 instancias nginx | kill -HUP directo al PID |
| 1.4 | Environment 404 | Ruta GCS incorrecta | Verificar estructura GCS |
| 1.5 | service nginx fail | Docker nginx ocupa puertos | No iniciar systemd nginx |
| 2.1 | WS no procesa jugadas | handleOnMessage limitado | GameManager bridge implementado |
| 2.2 | gRPC server vacío | Sin handlers reales | 3 endpoints implementados |
| 2.3 | Sin Redis | Estado no persistido | redis_store.go con save/load/delete |
| 3.1 | WebSocket sin token en Web export | Godot 4.4.1 Web export no envía custom headers | Token vía query string ?token=<jwt>, Go server lo extrae con r.URL.Query().Get("token") |
| 3.2 | _ready() no se ejecuta en Web export | Bug Godot 4.4.1 headless mode | Patrón init() llamado desde scene_controller.gd; _ready() queda vacío |
| 3.3 | Color(r,g,b) parse error en TSCN | Bug Godot 4.4.1 export headless con colores nested | Remover colores de .tscn, aplicar programáticamente vía _apply_styles() |
| 3.4 | Nginx /ws route 404 | Trailing slash en location /ws/ no matchea /ws | Cambiar a location /ws (sin slash al final) |
| 3.5 | player_position int vs string | Go model Position como int, handler enviaba "north" | type Position string en Go, handler envía "north" |
Versión: Godot 4.4.1 (headless mode usado para export)
_ready() no se ejecuta en Web export. Usar patrón init() llamado desde scene_controller.gd.Color(r,g,b) en TSCN nested causa parse error. Aplicar colores vía script.ws://host/ws?token=<jwt>.HTTPRequest funciona OK con REST API.https://cdn.bakuryu.com/...).assets/ui/ solo tienen domino_hero.png e icon.png — el resto vía CDN.Ver archivo ASSET_INVENTORY.md para inventario completo de los 1,187 assets en GCS, incluyendo: