# Big worlds

This page is for games with more on the map than on screen, or more simulation than one frame can always hold: colony sims, tycoons, factories and large tilemaps.

## Draw only what's in view

`camera.sees` tells you whether a world-space rectangle overlaps the view. `x` and `y` are its top-left corner:

```ts
draw.withCamera(game.camera, () => {
  for (const unit of units) {
    if (game.camera.sees(unit.x - 16, unit.y - 16, 32, 32)) draw.sprite(unit.frame, unit.x, unit.y);
  }
});
```

Tilemaps do this themselves when you pass the camera:

```ts
draw.withCamera(game.camera, () => map.draw(draw, { camera: game.camera }));
```

From 0.28, the dev tools tell you when this would help. If at least 1,000 sprites land outside the view and they're at least half of those drawn, you get a console warning, a line in the overlay's perf panel and `drawHint` in `game_profile`. They measure one frame in sixty, and only in dev. Sprites inside `draw.record` layers and render targets aren't counted, nor are `draw.sprites` calls with per-copy scale, interleaved data or a list of frames.

## Batch what's left

For many copies of one frame, use `draw.sprites` with a reused `Float32Array`:

```ts
draw.sprites(unitFrame, positions, count);
```

For scenery that doesn't change, record it once and replay it each frame:

```ts
const ground = draw.record((d) => {
  for (const t of tiles) d.sprite(t.frame, t.x, t.y, { origin: [0, 0] });
});
// each frame:
draw.withCamera(game.camera, () => draw.replay(ground));
```

See [Drawing](./drawing).

## Hold the frame rate when the simulation overruns

When steps take longer than a frame, the clock runs extra steps to catch up, those make the frame later still, and the game can fall to a few frames per second. From 0.28, `budgetMs` stops a frame catching up once its steps have used that many milliseconds:

```ts
const game = new Game({ canvas, width: 640, height: 360, clock: { budgetMs: 8 } });
```

The game then runs slower than real time at a steady frame rate. At least one step runs in every frame that has one due, and `game.step()` is unaffected, so scenarios and replays don't change.

Before 0.28, set a low `maxFrame` instead, such as `clock: { maxFrame: 1 / 30 }`. It limits the steps per frame, but it also slows the game on any display that runs below that rate.

## Move heavy work off the main thread

Pathfinding and big crowds usually cost the most. Spread searches over several steps, share one flow field between agents heading to the same place, and see [Using threads](./threads) for workers.

## Check that saves fit

`Storage` uses localStorage, which usually holds about 5 MB per origin. A write that doesn't fit leaves the last save that did, so from 0.28 `set` returns whether it reached storage:

```ts
if (!save.set("world", world)) showSaveFailed();
```

Before 0.28, `set` returned nothing and a failed write gave no sign. For larger saves, store them in IndexedDB yourself.