# Overview

> @dirtytalk/spatial tracks damage as rects on top of @dirtytalk/engine, so a 2D scene graph can repaint only the regions that changed.

Source: https://dirtytalk.42.dog/docs/spatial/

`@dirtytalk/spatial` is rect-based damage tracking for 2D scene graphs, built on [`@dirtytalk/engine`](/docs/engine/). Nodes report what changed and where; a `SceneRoot` batches the reports into one frame and hands your renderer the list of rects to repaint.

## Why a damage list

A dirty bit records only that something changed, so a renderer has to repaint everything. This package records a list of `Damage` entries instead. Each entry has a `rect` (the area affected) and a `kind` (`'data'`, `'layout'` or `'paint'`, which pipeline stages must run). The renderer receives the individual rects, so repaint cost scales with the damaged area rather than the scene size. See [Concepts](/docs/concepts/) for the underlying model and [Dirty channel](/docs/engine/dirty-channel/) for the engine primitive.

This package ships the `Renderer2D` interface, not a renderer. You supply the implementation (Canvas2D, WebGL, WebGPU, ...).

## Install

```sh
pnpm add @dirtytalk/spatial @dirtytalk/engine
```

## Minimal example

```ts
import { SceneNode, SceneRoot } from '@dirtytalk/spatial';
import type { Renderer2D, Rect } from '@dirtytalk/spatial';
import { SyncScheduler } from '@dirtytalk/engine';

const renderer: Renderer2D = {
  beginFrame(regions: readonly Rect[]) {
    console.log('repaint', regions);
  },
  endFrame() {},
};

class Button extends SceneNode {
  label = '';

  setLabel(label: string) {
    if (this.label === label) return;
    this.label = label;
    this.markDamaged('paint');
  }

  paint() {}
}

const root = new SceneRoot(renderer, {
  scheduler: new SyncScheduler(),
  bounds: { x: 0, y: 0, w: 800, h: 600 },
});

const button = new Button({ bounds: { x: 10, y: 10, w: 120, h: 40 } });
root.adoptChild(button); // repaint [{ x: 10, y: 10, w: 120, h: 40 }]

button.setLabel('Click me'); // repaint [{ x: 10, y: 10, w: 120, h: 40 }]
```

`SyncScheduler` flushes on every mark, which keeps the example linear. In an application, omit `scheduler` to use the default `RAFScheduler`, which coalesces all damage raised in a tick into one frame.

## Pages

- [Scene graph](/docs/spatial/scene-graph/): `SceneNode`, `SceneRoot`, damage kinds, batching, subclassing.
- [Rendering](/docs/spatial/rendering/): the `Renderer2D` contract, frame regions, `FrameTiming`, rect helpers, a Canvas2D renderer.
- [Pointer events](/docs/spatial/pointer-events/): hit-testing and `PointerRouter`.
- [API reference](/docs/spatial/api/): every public export with signatures.

For an interactive walkthrough, see [/learn/spatial/](/learn/spatial/).
