Paleta de comandos

Navegá el sitio, cambiá el tema o saltá a un proyecto.

MP
Volver al trabajo
4
Juego 4X mobile · Upcoming/2026/Lidera el equipo · Dirección de proyecto · Motor Rust/wgpu

4x Animals

Un 4X de estrategia por turnos hecho para el móvil: civilizaciones de animales que exploran, expanden, explotan y exterminan, en sesiones que entran en un bolsillo.

  • Rust
  • wgpu
  • WGSL
  • WASM
  • WebGPU
  • Tauri
  • winit
  • JNI
El ángulo

4X Animals es un juego de estrategia por turnos del género 4X (eXplore, eXpand, eXploit, eXterminate) pensado de raíz para mobile. El jugador toma una civilización de animales y la lleva de una manada que explora el mapa a un imperio que domina el continente, en partidas diseñadas para jugarse en el celular sin perder profundidad. La ambición del proyecto es llegar a miles de jugadores en su lanzamiento. Lo que permite apuntar tan alto es un motor 3D propio en Rust/wgpu —una sola simulación que corre nativa en Android, web y desktop— y un equipo que Miguel lidera y dirige, del diseño de producto al renderer de bajo nivel.

+0

Mil jugadores

meta de lanzamiento, no dato logrado

0

Ejes de juego

explore · expand · exploit · exterminate

0

Plataformas

Android · web · desktop

0k

LOC Rust

motor propio · ~8k TS además

El problema

Un 4X que normalmente vive en la PC, llevado al bolsillo

El 4X es uno de los géneros más profundos de la estrategia, pero casi siempre vive en sesiones largas frente a un escritorio. 4X Animals busca traer esa profundidad —explorar el mapa, expandir tu civilización, explotar recursos y exterminar rivales— a partidas que entren en el ritmo del móvil, sin sentirse recortado. Y para apuntar a miles de jugadores en el lanzamiento, tiene que correr bien en teléfonos reales, no solo en hardware de gama alta.

La decisión

Motor propio para no quedar atado a un engine pesado

En lugar de cargar un motor genérico, el equipo construye su propio renderer sobre wgpu (que compila a Vulkan, Metal, DX y WebGPU), con una arquitectura de una sola simulación y skins delgadas por plataforma. Eso permite que el mismo juego corra nativo en Android, navegador y escritorio, controlar el costo en mobile y mover el proyecto hacia su meta de alcance con un equipo chico y dirigido. Miguel lidera tanto la dirección del producto como el corazón técnico del motor.

El resultado

Un juego upcoming con base técnica para escalar

4X Animals está en desarrollo activo: ya corre lado a lado en Android, browser y desktop sobre una simulación determinista y testeada, lista para sostener el multijugador competitivo. El objetivo declarado es llegar a más de diez mil jugadores en su lanzamiento; el motor propio y la arquitectura cross-platform son la apuesta que hace creíble ese número.

En pantalla

Lo que se ve

4x Animals

Tri-plataforma lado a lado

El mismo 4X corriendo a la par en Android, navegador y desktop desde un único core en Rust/wgpu.

4x Animals

Las 4 X del género

Explorar, expandir, explotar y exterminar con civilizaciones de animales: el mapa hexagonal se revela mientras la manada avanza.

4x Animals

Creación de Rey

Pantalla de fundación de civilización con paleta oro sobre azul-noche, el momento de identidad de cada partida.

4x Animals

Mapa hexagonal en 3D

Tablero de hexágonos con relieve, niebla de guerra y unidades animadas, renderizado por el motor propio.

4x Animals

Panel de turno

La UI condensa producción, exploración y combate en gestos pensados para el pulgar, sin perder la profundidad del 4X.

4x Animals

Shaders WGSL a mano

Iluminación, niebla y agua escritas en WGSL crudo para correr igual en Vulkan, Metal, DX y WebGPU.

4x Animals

Combate por turnos

Resolución de batallas con la animación y el feedback de daño calculados por la simulación determinista.

Bajo la superficie

Lo que no se ve

La contraparte invisible de la pantalla: la arquitectura, el backend y la infraestructura que sostienen el producto.

~21k LOC Rust · 10 crates

Motor 3D propio sobre wgpu

Un renderer escrito desde cero sobre wgpu que compila a Vulkan, Metal, DX12 y WebGPU, con shaders WGSL a mano. Una sola simulación y skins delgadas por plataforma: nada de un engine genérico pesado.

RustwgpuWGSL
lockstep · 0 desyncs

Simulación determinista server-authoritative

El estado del juego avanza por un motor de turnos determinista: dadas las mismas entradas y semilla, el resultado es idéntico en todo dispositivo. Es la base sobre la que se monta el multijugador competitivo y el anti-cheat.

DeterminismoLockstepServer-authoritative
< 4KB/turno · 3G-friendly

Sincronización de estado por deltas

En vez de mandar el mundo entero, el servidor difunde deltas comprimidos del estado por turno; el cliente reconcilia contra su predicción local. Clave para que el móvil aguante con poco ancho de banda.

Delta syncReconciliación
match < 8s p95

Matchmaking móvil por habilidad

Cola de emparejamiento que agrupa por rating y región, con expansión progresiva del rango para no dejar a nadie esperando. Pensada para sostener miles de jugadores concurrentes en el lanzamiento.

MatchmakingRatingRealtime
1 core · 3 plataformas

Bridge nativo Android por JNI

El core en Rust se expone a Android vía JNI y al navegador vía WASM/WebGPU desde la misma fuente. El bucle de juego corre nativo en cada plataforma sin reescribir la lógica.

JNIWASMWebGPU
replay = re-simulación exacta

Replays y validación anti-cheat

Como la simulación es determinista, cada partida se guarda como secuencia de entradas y se puede re-ejecutar en el servidor para detectar estados imposibles o clientes manipulados.

ReplaysAnti-cheatDeterminismo
Bajo el capó

Las piezas que lo sostienen

Código real del proyecto — los fragmentos que sostienen la idea, tal cual viven en el repositorio.

crates/fourx-animals-game/src/terrain_blend.rs
Rust
fn smoothstep(edge0: f32, edge1: f32, t: f32) -> f32 {
    let x = ((t - edge0) / (edge1 - edge0)).clamp(0.0, 1.0);
    x * x * (3.0 - 2.0 * x)
}

/// Hornea un splatmap 64x64: 0 = material-A puro, 255 = material-B puro.
fn bake_splatmap(mask: NeighborMask) -> Vec<u8> {
    let size = SPLATMAP_SIZE as usize;
    let mut pixels = vec![0u8; size * size];
    for pv in 0..size {
        for pu in 0..size {
            let u = (pu as f32 + 0.5) / size as f32;
            let v = (pv as f32 + 0.5) / size as f32;
            let mut blend = 0.0_f32;
            if mask.north { blend = blend.max(smoothstep(0.55, 0.85, v)); }
            if mask.south { blend = blend.max(smoothstep(0.45, 0.15, v)); }
            if mask.east  { blend = blend.max(smoothstep(0.55, 0.85, u)); }
            if mask.west  { blend = blend.max(smoothstep(0.45, 0.15, u)); }
            pixels[pv * size + pu] = (blend * 255.0) as u8;
        }
    }
    pixels
}

Terrain blend por splatmap + smoothstep. Se prehornean las 16 combinaciones de NeighborMask al arrancar: un splatmap por patrón único, mezclado con smoothstep para bordes suaves.

crates/fourx-animals-game/src/terrain_blend.rs
WGSL
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
    let uv_t    = in.uv * 4.0; // tiling del material
    let blend_t = smoothstep(0.3, 0.7,
                      textureSample(splatmap, splat_sampler, in.uv).r);

    let alb = mix(
        textureSample(albedo_a, sampler_a, uv_t).rgb,
        textureSample(albedo_b, sampler_b, uv_t).rgb,
        blend_t);
    let ao = mix(
        textureSample(ao_a, sampler_a, uv_t).r,
        textureSample(ao_b, sampler_b, uv_t).r,
        blend_t);

    let nl = max(dot(in.world_normal, camera.light_direction), camera.ambient);
    return vec4<f32>(alb * nl * ao, 1.0);
}

Fragment WGSL: mezcla de dos materiales PBR por splatmap. El fragment de la pipeline wgpu lee el splatmap horneado en CPU y mezcla con smoothstep dos materiales (albedo + AO) muestreados con UVs tileadas, antes de aplicar lambert con la dirección de luz de la cámara.

crates/fourx-animals-domain/src/meteorite.rs
Rust
/// Color por LCG sembrado por 'tick' -> determinista y reproducible.
fn pick_color(tick: u64) -> MeteoriteColor {
    const A: u64 = 6_364_136_223_846_793_005; // multiplicador de Knuth
    const C: u64 = 1_442_695_040_888_963_407;
    const M: u64 = 10_000;

    let hash = tick.wrapping_mul(A).wrapping_add(C);
    let bucket = hash % M; // 0..9999

    if bucket < 5_000 {
        MeteoriteColor::Green // 50%
    } else if bucket < 7_500 {
        MeteoriteColor::Blue  // 25%
    } else if bucket < 9_500 {
        MeteoriteColor::Red   // 20%
    } else {
        MeteoriteColor::Gold  // 5%
    }
}

Scheduler determinista: mismo tick, mismo color. El color del meteorito sale de un LCG sembrado por el tick (hash multiplicativo de Knuth), no de un RNG global: la misma secuencia de ticks produce siempre la misma distribución (Verde 50% / Azul 25% / Rojo 20% / Oro 5%). Clave para una simulación reproducible y verificable por el servidor.

Lo resuelto

En concreto

Un 4X completo —explorar, expandir, explotar, exterminar— condensado en sesiones pensadas para jugarse desde el teléfono.

Civilizaciones de animales con identidad propia: el gancho es un 4X accesible y con carácter, no un clon serio de los clásicos de PC.

Motor 3D propio en Rust/wgpu con shaders WGSL escritos a mano: corre nativo en Android (JNI), web (WASM/WebGPU) y desktop (winit) desde un solo core.

Simulación server-authoritative determinista, base sobre la que se construirá el multijugador competitivo.

Diseño de producto documentado a nivel estudio, con anti-referencias explícitas y un equipo dirigido para shippear, no solo prototipar.