Files
fn_registry/dev/issues/0072j-gamedev-physics-box2d.md
T

6.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
0072j gamedev — physics 2D (Box2D integration + funciones registry) pendiente feature
gamedev
multi-app media
0072b
2026-05-10 2026-05-17
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

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

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

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

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

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

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

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

  • Funciones registradas en registry con .md + tests.
  • App physics_test --self-test pasa.
  • Demo: caja cae sobre suelo, rebota, se detiene (en engine_demo de 0072k).
  • Debug draw funcional (toggle en menu del editor).
  • Tamaño contribuye ≤ 250 KB al wasm gzip.
  • 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).