Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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 |
|
multi-app | media |
|
2026-05-10 | 2026-05-17 |
|
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:
- Fixed timestep (no variable dt).
- Mismas operaciones en mismo orden.
- Misma version del compilador (con cuidado de
-ffast-mathque 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:
- Crea world, body, shape.
- Step 100 frames.
- Verifica que el body cae (gravity).
- Verifica raycast contra un static body.
- Verifica contact event tras colision.
- 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-testpasa. - Demo: caja cae sobre suelo, rebota, se detiene (en
engine_demode 0072k). - Debug draw funcional (toggle en menu del editor).
- Tamaño contribuye ≤ 250 KB al wasm gzip.
- Documentacion
cpp/GAMEDEV.mdseccion 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).