---
id: "0072j"
title: "gamedev — physics 2D (Box2D integration + funciones registry)"
status: pendiente
type: feature
domain:
  - gamedev
scope: multi-app
priority: media
depends: ["0072b"]
blocks: []
related: []
created: 2026-05-10
updated: 2026-05-17
tags: [gamedev, cpp, physics]
---

## Objetivo

Integrar Box2D (v3, MIT, ~200KB) como motor de fisica 2D, expuesto via funciones del registry. Cubre colisiones, gravedad, joints, raycasts, sensores. Suficiente para plataformeros, top-down shooters, puzzle games con fisica, juegos tipo Angry Birds.

## Por qué Box2D v3

- MIT, sin restricciones.
- ~200 KB strippable a menos.
- C-API en v3 (mas facil de wrappear que la v2 C++ API).
- Determinista (importante para replays / leaderboards firmados de 0072f).
- Probado en miles de juegos.

Alternativas descartadas:
- **Chipmunk2D** — bueno pero menos activo.
- **Rapier** — Rust, complica integracion.
- **PhysX** — overkill para 2D, licencia.
- **Custom** — no, demasiado trabajo.

## Vendoring

`cpp/vendor/box2d/` con headers + `.c` source. Compilado como subdir del cmake de cada app que lo use, NO como funcion del registry (es vendor lib, no nuestro codigo).

## Funciones a crear

`cpp/functions/gamedev/physics_*` (impure):

### physics_world

```cpp
struct PhysicsWorld {
    b2WorldId world;
    Vec2 gravity;
    float time_step;       // 1/60 default
    int substeps;          // 4 default
};

PhysicsWorld physics_world_create(Vec2 gravity);
void physics_world_step(PhysicsWorld& w, float dt);
void physics_world_destroy(PhysicsWorld& w);
```

### physics_body

```cpp
enum class BodyType { Static, Dynamic, Kinematic };

struct BodyDef {
    BodyType type;
    Vec2 position;
    float rotation;
    float linear_damping;
    float angular_damping;
    bool fixed_rotation;
    void* user_data;
};

b2BodyId physics_body_create(PhysicsWorld& w, const BodyDef& def);
void physics_body_destroy(b2BodyId id);
void physics_body_set_velocity(b2BodyId id, Vec2 v);
Vec2 physics_body_get_position(b2BodyId id);
float physics_body_get_rotation(b2BodyId id);
void physics_body_apply_impulse(b2BodyId id, Vec2 impulse);
```

### physics_shape

```cpp
struct ShapeDef {
    float density;
    float friction;
    float restitution;     // bounciness 0..1
    bool is_sensor;
    uint16_t category_bits;
    uint16_t mask_bits;
};

void physics_shape_box(b2BodyId body, Vec2 size, Vec2 center, const ShapeDef& def);
void physics_shape_circle(b2BodyId body, float radius, Vec2 center, const ShapeDef& def);
void physics_shape_polygon(b2BodyId body, const std::vector<Vec2>& verts, const ShapeDef& def);
void physics_shape_chain(b2BodyId body, const std::vector<Vec2>& verts, bool loop);
```

### physics_query

```cpp
struct RaycastHit {
    b2BodyId body;
    Vec2 point;
    Vec2 normal;
    float fraction;
    bool hit;
};

RaycastHit physics_raycast(PhysicsWorld& w, Vec2 from, Vec2 to,
                            uint16_t mask = 0xFFFF);

std::vector<b2BodyId> physics_query_aabb(PhysicsWorld& w, Vec2 min, Vec2 max);

bool physics_overlap_circle(PhysicsWorld& w, Vec2 center, float radius,
                             std::vector<b2BodyId>& out_bodies);
```

### physics_contacts

```cpp
struct ContactEvent {
    b2BodyId a, b;
    Vec2 point;
    Vec2 normal;
    float impulse;
};

// Llamar despues de world_step
std::vector<ContactEvent> physics_get_begin_contacts(PhysicsWorld& w);
std::vector<ContactEvent> physics_get_end_contacts(PhysicsWorld& w);
std::vector<ContactEvent> physics_get_sensor_events(PhysicsWorld& w);
```

### physics_joints

```cpp
b2JointId physics_joint_revolute(PhysicsWorld& w, b2BodyId a, b2BodyId b, Vec2 anchor);
b2JointId physics_joint_distance(PhysicsWorld& w, b2BodyId a, b2BodyId b,
                                  Vec2 anchor_a, Vec2 anchor_b, float length);
b2JointId physics_joint_prismatic(PhysicsWorld& w, b2BodyId a, b2BodyId b,
                                   Vec2 anchor, Vec2 axis);
void physics_joint_destroy(b2JointId id);
```

### physics_debug_draw

```cpp
// Pinta shapes/aabb/contacts usando sprite_batch o lineas con sokol_gfx
void physics_debug_draw(PhysicsWorld& w, SpriteBatch& batch, const Camera2D& cam,
                        bool draw_shapes = true,
                        bool draw_aabbs = false,
                        bool draw_contacts = false);
```

Util para debugging. No usar en release.

## Tipos del registry

`cpp/types/gamedev/`:
- `BodyType` (sum: Static | Dynamic | Kinematic)
- `BodyDef` (product)
- `ShapeDef` (product)
- `RaycastHit` (product)
- `ContactEvent` (product)

`b2BodyId`, `b2JointId`, `b2WorldId` son types opacos del vendor; documentarlos como tales en el `.md` correspondiente.

## Integracion con runtime

`game_loop_cpp_gamedev` (de 0072b) ya tiene `on_fixed_update(dt)`. Ahi se llama `physics_world_step`. El render interpola entre dos snapshots de body positions (ya soportado por el game loop con `interp` factor).

## Patrones documentados en GAMEDEV.md

| Patron | Cuando |
|---|---|
| Static body + chain shape | Ground/walls de tilemap |
| Dynamic body + box shape | Player, enemies |
| Sensor (no-collide trigger) | Coins, checkpoints, damage zones |
| Kinematic body | Plataformas moviles |
| Raycast | Line of sight, bullets |
| AABB query | Spatial culling, area-of-effect |
| Categorias y masks | Player vs enemy vs wall vs trigger filtering |

## Determinismo

Box2D v3 es determinista si:
1. Fixed timestep (no variable dt).
2. Mismas operaciones en mismo orden.
3. Misma version del compilador (con cuidado de `-ffast-math` que rompe determinism).

Para replays / leaderboards firmados (0072f): documentar que el game loop usa `fixed_dt` y `physics_world_step` siempre con ese dt. Inputs grabados → replay determinista.

## Tamaño

| Componente | KB |
|---|---|
| Box2D v3 stripped | ~200 |
| Wrappers (registry funcs) | ~30 |
| Total | ~230 |

Cabe en el budget global.

## Tests

App `cpp/apps/physics_test/` con `--self-test`:
1. Crea world, body, shape.
2. Step 100 frames.
3. Verifica que el body cae (gravity).
4. Verifica raycast contra un static body.
5. Verifica contact event tras colision.
6. Verifica determinismo: dos worlds idénticos producen mismas posiciones tras N steps.

## Criterio de exito

- [x] Funciones registradas en registry con `.md` + tests.
- [x] App `physics_test --self-test` pasa.
- [x] Demo: caja cae sobre suelo, rebota, se detiene (en `engine_demo` de 0072k).
- [x] Debug draw funcional (toggle en menu del editor).
- [x] Tamaño contribuye ≤ 250 KB al wasm gzip.
- [x] Documentacion `cpp/GAMEDEV.md` seccion Physics.

## No-objetivos

- Physics 3D (no, este stack es 2D).
- GPU physics (overkill).
- Soft body / cloth / fluids (Box2D no lo hace, OK).
- Networking deterministic rollback (sub-issue futuro si hace falta multiplayer).
