arcane-engine 0.26.1

Arcane game engine — agent-native 2D engine with embedded TypeScript runtime
# SDF: Procedural Vector Graphics

SDF (Signed Distance Fields) lets you create resolution-independent shapes entirely in code. No image assets needed. Shapes are defined as TypeScript functions that compile to GPU shaders.

## When to Use SDF

**Good for:**
- Procedural backgrounds (mountains, clouds, terrain)
- UI elements (buttons, panels, health bars)
- Particle-like effects (glows, stars, hearts)
- Prototyping before you have art assets
- Effects that need to scale to any resolution

**Not ideal for:**
- Detailed character sprites (use sprite sheets)
- Complex textures (use images)
- Pixel art aesthetics (use sprites)

## Quick Start

```typescript
import {
  sdfCircle, sdfBox, sdfTriangle, sdfStar, sdfHeart,
  sdfUnion, sdfSmoothUnion, sdfOffset, sdfRound,
  sdfEntity, clearSdfEntities, flushSdfEntities,
  solid, gradient, glow,
  LAYERS,
} from "@arcane/runtime/rendering";

// In onFrame:
clearSdfEntities();

sdfEntity({
  shape: sdfCircle(30),
  fill: solid("#ff0000"),
  position: [100, 200],
  layer: LAYERS.ENTITIES,
});

flushSdfEntities();
```

## Gradient Scale Parameter

When using gradients on non-square shapes, the gradient may not span the full shape. Use the `scale` parameter to fix this:

```typescript
// Problem: bounds must fit width, but gradient spans full bounds height
sdfEntity({
  shape: sdfTriangle([0, 37], [-43, -37], [43, -37]),
  fill: gradient("#000066", "#ff0000", 90),         // Gradient doesn't reach edges!
  bounds: 43,
});

// Solution: scale adjusts gradient to fit actual shape extent
sdfEntity({
  shape: sdfTriangle([0, 37], [-43, -37], [43, -37]),
  fill: gradient("#000066", "#ff0000", 90, 43 / 37),  // scale = bounds / half-height
  bounds: 43,
});
```

**The formula:** `scale = bounds / (shape_extent_in_gradient_direction / 2)`

For a 90° gradient (bottom-to-top), use the shape's Y extent. For 0° (left-to-right), use X extent.

## Bounds: Clipping vs. Gradient

The `bounds` parameter serves two purposes:
1. **Clipping:** Only pixels within ±bounds from the entity center are rendered
2. **Gradient mapping:** Gradients span from -bounds to +bounds

If your shape extends beyond bounds, it gets clipped. If your shape is smaller than bounds, gradients won't reach the edges (unless you use the scale parameter).

**Common pattern for wide shapes:**
```typescript
sdfEntity({
  shape: wideShape,
  fill: gradient("#green", "#white", 90, boundsX / shapeHalfHeight),
  position: [SCREEN_WIDTH / 2, y],
  bounds: SCREEN_WIDTH / 2,
});
```

## Shape Composition

### Boolean Operations

```typescript
sdfUnion(a, b, c)           // Combine shapes (sharp edges)
sdfSmoothUnion(r, a, b, c)  // Combine with rounded blend (r = blend radius)
sdfSubtract(a, b)           // Cut b from a
sdfIntersect(a, b)          // Keep only overlap
```

### Transforms

```typescript
sdfOffset(shape, x, y)      // Move shape
sdfRound(shape, r)          // Round corners by r pixels
sdfScale(shape, s)          // Scale shape (bakes into shader)
sdfRotate(shape, degrees)   // Rotate (bakes into shader)
```

**Performance tip:** Use `sdfEntity({ rotation, scale })` for animation instead of `sdfRotate()`/`sdfScale()`. Instance transforms are GPU-efficient; shape transforms cause shader recompilation.

### Domain Transforms

```typescript
sdfRepeat(shape, spacingX, spacingY)  // Infinite tiling
sdfMirrorX(shape)                      // X-axis symmetry
```

## Fill Types

```typescript
solid("#ff0000")                           // Flat color
gradient("#blue", "#red", 90)              // Linear gradient (angle in degrees)
gradient("#blue", "#red", 90, 1.5)         // With scale for non-square shapes
glow("#ff0000", 0.25)                      // Soft glow (lower intensity = larger)
solidOutline("#fill", "#stroke", 2)        // Fill with outline
outlineFill("#stroke", 2)                  // Outline only
cosinePalette(a, b, c, d)                  // Animated rainbow palette
```

## Animation Helpers

Use these with `sdfEntity` instance properties for efficient GPU animation:

```typescript
pulse(time, speed, min, max)    // Oscillating scale
spin(time, degreesPerSecond)    // Continuous rotation
bob(time, speed, amplitude)     // Vertical bobbing
breathe(time, speed, min, max)  // Opacity pulse

sdfEntity({
  shape: sdfStar(20, 5, 0.4),
  fill: glow("#gold", 0.2),
  position: [x, y],
  scale: pulse(time, 2, 0.9, 1.1),
  rotation: spin(time, 45),
  opacity: breathe(time, 3, 0.7, 1.0),
  bounds: 60,
});
```

## Coordinate System

SDF uses math coordinates: **+Y is up**, **+X is right**. This differs from screen coordinates where Y increases downward.

- `sdfOffset(shape, 10, 20)` moves shape right and **up**
- `gradient(..., 90)` goes from bottom to top
- Triangle `[0, 50]` is the top vertex, `[0, -50]` is bottom

## Performance Tips

1. **Reuse shape definitions** - Define shapes at module scope, not in onFrame
2. **Use instance transforms** - `rotation`, `scale`, `opacity` on sdfEntity are GPU-efficient
3. **Avoid shape transforms in loops** - `sdfRotate()`, `sdfScale()` on shapes cause shader recompilation
4. **Set explicit bounds** - Auto-calculated bounds may be larger than needed
5. **Batch similar shapes** - Shapes with identical SDF expressions share shaders