# World & physics

> Create discs, walls, goals and lines in the physical world of a running match.

During a match, your game has a physical **world**: the ball, the players'
discs, the walls, the goals. You create these objects and set their
properties; disko moves them, makes them bounce off each other, and tells
you when something interesting happens.

You never write the physics yourself. There's no "move the ball by its
velocity" or "check if two circles overlap" in a disko game. That work is
done by disko, the same way on every player's screen.

## When the world exists

The world belongs to a match. It's created when a match starts and thrown
away when it stops, so you build your field in the `start` handler. The
`world` global is only usable between `start` and `stop`; using an object
after its match ended throws a `StaleHandleError`.

## What happens in one tick

A running match advances in **ticks**, 60 per second. In each tick, disko
applies the players' inputs, runs your code, moves everything, and then
tells you what happened:

```mermaid alt="Each tick: players' inputs are applied; your tick handler runs; disko moves discs and resolves collisions; then your crossing, sensor and post-tick handlers run with what happened. Then the next tick begins."
flowchart TB
  input["Players' inputs<br/>applied"] --> tick["Your tick<br/>handler"]
  tick --> physics["disko moves<br/>and collides"]
  physics --> after["Your crossing, sensor<br/>and post-tick handlers"]
  after -- "next tick" --> input
```

- `tick` runs before the physics step: a good place to change velocities
  or rules for this tick.
- `post-tick` runs after it, along with `crossing` and sensor events: a good
  place to react to where things ended up, such as a goal.

Both handlers receive the tick number and the events of that turn, in the
order they were delivered.

## The objects

| Create with           | Object     | What it's for                                 |
| --------------------- | ---------- | --------------------------------------------- |
| `world.createDisc`    | `Disc`     | balls and player discs                        |
| `world.createPlane`   | `Plane`    | infinite straight walls                       |
| `world.createVertex`  | `Vertex`   | fixed points, and the ends of segments        |
| `world.createSegment` | `Segment`  | straight or curved walls between two vertices |
| `world.createJoint`   | `Joint`    | keeping two discs at a distance               |
| `world.crossing`      | `Crossing` | lines that report discs crossing them         |
| `world.visual`        | `Visual`   | labels and rings that don't collide           |

This builds a small field: four walls, a ball, a goal post and a goal line.

```ts title="src/field.ts"
game.on("start", () => {
  // Walls: four planes facing into a 600 × 300 field.
  world.createPlane({ normal: { x: 1, y: 0 }, distance: -300 });
  world.createPlane({ normal: { x: -1, y: 0 }, distance: -300 });
  world.createPlane({ normal: { x: 0, y: 1 }, distance: -150 });
  world.createPlane({ normal: { x: 0, y: -1 }, distance: -150 });

  // A ball.
  const ball = world.createDisc({ radius: 10, mass: 1, damping: 0.99 });
  ball.setRestitution(0.9);
  ball.setAppearance({ fill: "#ffffff", stroke: "#000000", strokeWidth: 2 });

  // A goal post: a segment between two vertices.
  world.createSegment({
    start: world.createVertex({ x: 280, y: -50 }),
    end: world.createVertex({ x: 280, y: 50 }),
  });

  // A goal line that reports discs crossing it.
  world.crossing({ start: { x: 290, y: -50 }, end: { x: 290, y: 50 } });
});
```

Every object has setters for its properties, such as `disc.setVelocity`,
`disc.setMass` or `segment.setCurve`. `state()` returns where an object is and
how it's moving at the current tick; a disc's `DiscState` has its position,
velocity, radius and collision properties. `world.destroy(object)` removes an
object.

### How a disc looks

`disc.setAppearance({ fill, stroke, strokeWidth })` sets a disc's colors:
`#rrggbb` or `#rrggbbaa` colors and an outline from 0 to 64 CSS pixels wide
(0 hides it). All three are required. A new disc is `#f5f7fb` with a black
2-pixel outline. For an image instead of a color, see
[Assets](/games/assets).

## Who collides with what

Not everything should bump into everything. In football, players shouldn't
walk into the goal net, but the ball should bounce off it.

That's what **collision groups** are for. Each object belongs to groups
(`addGroup`, `removeGroup`) and lists the groups it collides with
(`setAcceptedGroups`). Two objects only collide if each accepts the other.

Some objects shouldn't collide at all, only notice contact:

- A **sensor** reports `sensor-enter` and `sensor-leave` instead of bouncing,
  for example to detect a player standing in an area.
- A **crossing** is a line that reports discs passing through it. It never
  blocks anything, which makes it the natural goal line. It has groups too,
  so it can ignore players and only watch the ball.

Every collidable object also has a **restitution**: how bouncy its collisions
are, from 0 (dead stop) to 1 (no energy lost).

## Reacting to crossings

`crossing` fires when a disc crosses a crossing line. It tells you which disc
(`object`), which way (`forward` or `backward`), where, and the `fraction` of
the tick's movement at which the crossing happened. This resets the ball when
it crosses forward:

```ts title="src/field.ts"
game.on("crossing", ({ object, direction }) => {
  if (object !== null && direction === "forward") {
    object.setPosition({ x: 0, y: 0 });
    object.setVelocity({ x: 0, y: 0 });
  }
});
```

## Ending a match

`stop` reports the final tick and why the match ended: `finished` after your
game calls `game.finish()`, or `stopped` when the room stopped it.

## Visuals

Visuals are drawings with no physical presence, such as a name above a player
or a ring under the disc you control. `world.visual({ anchor, appearance, audience })`
attaches a text label or a ring to a disc or a position, and `audience`
chooses which players see it. `visual.update(patch)` changes it and
`visual.destroy()` removes it.

See the [`world` reference](/reference/world) for every option.
