GravityMass
How hard an entity pulls, and how big a target it is.
What it does
GravityMass is data with no System of its own. Each frame GravityBodySystem reads every mass where its MeshPrimitive sits and moves every GravityBody through the sum of their pulls. Gravity never moves a mass that is not also a body, so a black hole stays where the player left it.
The units make G equal 1. Each mass pulls with
where d runs from the body to the mass. Far away that is the plain mass / r²: a hole of 600 pulls a rock 8 units out at about 9.4 units/s². Close in, the softening keeps it finite, so a rock that skims a point mass gets a hard shove instead of a huge kick in one step.
A negative mass pushes. The same sum with the sign flipped points away from the mass, and nothing downstream changes. That is all a white hole is.
The rock and the aim line drawn ahead of it (TrajectoryPreview) are pushed by one shared function, fieldAt, which is why the line can be trusted. For level design, circularSpeed gives the speed of a circular orbit at a distance:
A rock that passes a 600 hole 8 units out at well over 8.66 units/s sails by with a bend. Well under, it falls in.
Use it in a scene
A scene is just data — a list of entities, each with its components. At startup the SceneLoader turns this JSON into a live world; edit the file and reload to rebuild it.
Build it from scratch with the bjs CLI:
- 1Scaffold a project
World, renderer, and dev server — ready to run.
- 2Install dependencies
- 3Add GravityMass
Copies its source into src/ so the scene resolves.
First time? Run bjs login once.
- 4Paste the scene into src/scenes/arcade-room.ts and run
SceneLoader builds the world from the JSON; reload to rebuild.
The hole swallows what reaches it and the asteroid is a crash. The rock is a body and a mass at once: pulled by everything, and pulling a little itself.
Props
mass(number, default1) — how hard it pulls. Negative pushes.radius(number, default0) — the surface a body hits. 0 is a point mass that pulls but can never be hit.softening(number, default0.25) — keeps the pull finite near the centre. Far out it barely matters.absorbs(boolean, defaultfalse) — makes the surface an event horizon: a body that reaches it is swallowed rather than crashing.lenses(boolean, default: same asabsorbs) — whether a GravityLens sky bends light round this mass. Black holes lens, rocks don't.
Events
None. When a body reaches the surface, GravityBody emits gravity.body.contact naming this mass, with swallowed: true if it absorbs.
Dependencies
None in its own bundle. It needs a MeshPrimitive on the same entity for its position, and a GravityBody somewhere in the scene to read it.
Notes
radiusis not read from the mesh. Size the sphere to match, as the example does, or let BlackHole and WhiteHole scale theirs for you.mass: 0with aradiusis a wall that pulls nothing.radius: 0is a pull nothing can hit.- Masses are read fresh every frame. Drag a hole and the next frame's pull follows it; park one in its pool and it stops pulling at once.
- A body never feels its own mass. SpaceGolf rocks carry
mass: 0.3, so they tug on each other in flight. - The scene loader starts GravityBody's System only when the scene names
GravityBodysomewhere, and a Pool blueprint counts. Masses on their own move nothing.










































































