---
id: "0072d"
title: "gamedev — WASM build pipeline + size budget"
status: pendiente
type: feature
domain:
  - gamedev
scope: multi-app
priority: alta
depends: ["0072a", "0072b"]
blocks: []
related: []
created: 2026-05-10
updated: 2026-05-17
tags:
  - gamedev
  - wasm
  - infra
---

## Objetivo

Pipeline reproducible de build WASM con presupuestos de tamaño estrictos y herramientas de auditoria. WASM es la plataforma prioritaria por la integracion crypto (sub-issues 0072e, 0072f), y el peso del bundle es el factor de retencion mas critico (cada 100KB extra = ~1% de drop-off en navegador).

## Presupuestos

| Artefacto | Limite duro | Limite blando |
|---|---|---|
| `*.wasm` raw | 5 MB | 4 MB |
| `*.wasm.gz` (gzip -9) | 2 MB | 1.5 MB |
| `*.wasm.br` (brotli -11) | 1.5 MB | 1.2 MB |
| `*.js` (loader) gzip | 50 KB | 30 KB |
| `*.html` (shell) | 10 KB | 5 KB |
| Bundle assets `.pak` | 5 MB | 3 MB |

Total objetivo: **< 4 MB descargados** (wasm.br + js.gz + assets.gz) para "first paintable scene".

## Pipeline

`bash/functions/pipelines/build_wasm_cpp_pipelines.sh`:

```bash
#!/usr/bin/env bash
# build_wasm <app_name> [--release|--debug] [--no-budget-check]
set -euo pipefail

APP="$1"; shift
MODE="${1:-release}"
SRC_DIR="cpp/apps/$APP"
BUILD_DIR="build/wasm/$APP"

# 1. emsdk
[ -d emsdk ] || git clone https://github.com/emscripten-core/emsdk
(cd emsdk && ./emsdk install latest && ./emsdk activate latest)
source emsdk/emsdk_env.sh

# 2. cmake
emcmake cmake -B "$BUILD_DIR" -S "$SRC_DIR" \
    -DCMAKE_BUILD_TYPE=$([ "$MODE" = "release" ] && echo MinSizeRel || echo Debug)
cmake --build "$BUILD_DIR" -j

# 3. compress
WASM="$BUILD_DIR/$APP.wasm"
gzip -9 -k "$WASM"
brotli -q 11 -k "$WASM"

# 4. report
echo "── Sizes ──"
ls -lah "$BUILD_DIR/$APP".{wasm,wasm.gz,wasm.br,js,html}

# 5. budget check
if [ "${1:-}" != "--no-budget-check" ]; then
    bash bash/functions/gamedev/wasm_size_budget_check.sh "$BUILD_DIR" "$APP"
fi
```

## Funcion de auditoria

`bash/functions/gamedev/wasm_size_budget_check.sh`:

```bash
#!/usr/bin/env bash
# Falla con exit 1 si excede el budget duro
WASM_GZ="$BUILD_DIR/$APP.wasm.gz"
SIZE=$(stat -c%s "$WASM_GZ")
LIMIT=$((2 * 1024 * 1024))
if [ "$SIZE" -gt "$LIMIT" ]; then
    echo "❌ $WASM_GZ = $SIZE bytes > $LIMIT (2 MB)"
    exit 1
fi
echo "✓ $WASM_GZ within budget"
```

## Banderas de compilacion (MinSizeRel)

```cmake
if(EMSCRIPTEN)
    target_compile_options(<target> PRIVATE
        -Os                          # Optimize for size
        -fno-exceptions              # No C++ exceptions (huge win)
        -fno-rtti                    # No RTTI
        -ffunction-sections
        -fdata-sections
    )
    target_link_options(<target> PRIVATE
        -Os
        -sASSERTIONS=0               # No runtime assertions in release
        -sFILESYSTEM=0               # No emscripten filesystem (use fetch)
        -sMINIMAL_RUNTIME=2          # Slim JS runtime
        -sENVIRONMENT=web            # Browser only, no node
        -sUSE_WEBGL2=1
        -sFULL_ES3=1
        -sALLOW_MEMORY_GROWTH=1
        -sINITIAL_MEMORY=33554432
        -sSTACK_SIZE=1048576
        --closure=1                  # Closure compiler on JS glue
        -Wl,--gc-sections
    )
endif()
```

`MINIMAL_RUNTIME=2` ahorra ~50KB de JS glue. `FILESYSTEM=0` ahorra otros ~30KB. `--closure=1` reduce JS otro ~30%.

## Herramientas de auditoria

Cuando el budget se rompe, herramientas para encontrar el culpable:

1. **`twiggy`** (Rust tool, MIT) — analiza wasm y lista funciones por tamaño:
   ```bash
   twiggy top -n 50 build/wasm/engine_smoke.wasm
   ```
2. **`wasm-objdump -h`** — secciones del binario.
3. **`emcc --emit-symbol-map`** — mapa de simbolos para correlacionar con codigo C++.
4. **Bloaty** (Google, ASL2) — analisis size attribution.

Funcion: `bash/functions/gamedev/wasm_size_audit.sh <wasm_path>` — corre las 4 y produce reporte markdown.

## Tecnicas de reduccion (cuando se exceda)

Documentar en `cpp/GAMEDEV.md`:

1. **No usar `<iostream>`** — ~80KB de runtime. Usar `printf` o nada.
2. **No usar `<filesystem>`** — pesado. SDL_RWops o emscripten fetch.
3. **`std::function` → punteros a funcion** cuando se pueda. ~5KB por instancia.
4. **`std::regex` prohibido** — ~100KB. Usar parsers manuales o `re2` mini.
5. **STL containers** — usar pero con `-fno-exceptions`. `std::vector` OK, `std::map` evitar (preferir `std::vector<pair>` ordenado o `flat_hash_map` mini).
6. **Templates** — instanciar pocas veces. Cada nueva instanciacion es codigo nuevo.
7. **Inlining** — `-Os` ya lo balancea. No forzar `__forceinline`.
8. **Vendor libs** — auditar antes de añadir. Cada lib pasa por size review en su PR.

## Streaming + caching

Para juegos mas grandes en el futuro:
- `--preload-file` empotra assets en `.data` adyacente al wasm. Cargado de golpe.
- `fetch()` async para assets opcionales (niveles avanzados, musica).
- Service Worker para cache offline (en sub-issue futuro, no urgente).

## Integracion con CI

GitHub Actions / Gitea Actions workflow `gamedev-wasm-budget.yml`:
- Trigger: PR que toca `cpp/apps/engine_*` o `cpp/functions/gfx/sg_*`
- Build WASM
- Comprueba budgets
- Comenta tamaños en el PR
- Falla si rompe limite duro

## Criterio de exito

- [x] Pipeline `build_wasm_cpp_pipelines.sh` reproducible en Linux + Windows WSL.
- [x] `engine_smoke` (de 0072a) pasa budget check.
- [x] `wasm_size_audit.sh` produce reporte legible.
- [x] CI bloquea PRs que rompen el budget.
- [x] Documentacion `cpp/GAMEDEV.md` actualizada con reglas de tamaño.

## No-objetivos

- WebGPU (sokol_gfx WebGPU backend es alpha) — diferido.
- WASM SIMD — opcional, evaluar despues de medir hot paths.
- Multi-threading WASM (SharedArrayBuffer + COOP/COEP headers) — overkill, lo dejamos para juegos que lo pidan.
