Files
fn_registry/dev/issues/0072d-gamedev-wasm-build-size-budget.md

5.7 KiB

id, title, status, type, domain, scope, priority, depends, blocks, related, created, updated, tags
id title status type domain scope priority depends blocks related created updated tags
0072d gamedev — WASM build pipeline + size budget pendiente feature
gamedev
multi-app alta
0072a
0072b
2026-05-10 2026-05-17
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:

#!/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:

#!/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)

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:
    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

  • Pipeline build_wasm_cpp_pipelines.sh reproducible en Linux + Windows WSL.
  • engine_smoke (de 0072a) pasa budget check.
  • wasm_size_audit.sh produce reporte legible.
  • CI bloquea PRs que rompen el budget.
  • 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.