arcane-engine 0.26.1

Arcane game engine — agent-native 2D engine with embedded TypeScript runtime
# Juice & Impact

High-level APIs that orchestrate multiple subsystems (camera shake, screen flash, particles, audio, hitstop) in a single call. These are the "one call, big payoff" functions that make a game feel good.

## Impact Combinator

One call triggers shake + hitstop + flash + particles:

```typescript
import { impact, impactLight, impactHeavy, isHitstopActive, getHitstopFrames } from "@arcane/runtime/rendering";
import { updateTweens } from "@arcane/runtime/tweening";
import { updateParticles } from "@arcane/runtime/particles";

// Full custom impact
impact(enemy.x, enemy.y, {
  shake: { intensity: 8, duration: 0.2 },
  hitstop: 3,  // freeze gameplay for 3 frames
  flash: { r: 1, g: 1, b: 1, duration: 0.1, opacity: 0.6 },
  particles: { count: 20, color: { r: 1, g: 0.5, b: 0, a: 1 } },
});

// Presets — use these for quick juice
impactLight(enemy.x, enemy.y);   // small shake + brief flash
impactHeavy(boss.x, boss.y);     // big shake + long flash + particles
```

## Frame Loop with Hitstop

Hitstop freezes gameplay (but not visuals) for N frames. Wire it into your frame loop:

```typescript
game.onFrame((ctx) => {
  if (!isHitstopActive()) {
    // Normal gameplay — skipped during hitstop
    state = updateGameplay(state, ctx.dt);
  }
  // Always run, even during hitstop
  updateTweens(ctx.dt);
  updateParticles(ctx.dt);
  renderGame(state);
});
```

## When to Use Impact

- **Player hit by enemy**`impactLight(player.x, player.y)`
- **Enemy dies**`impact(enemy.x, enemy.y, { particles: { count: 15 }, shake: { intensity: 4 } })`
- **Boss attack**`impactHeavy(boss.x, boss.y)`
- **Player lands from height**`impactLight(player.x, player.y + player.h)` with small shake
- **Breakable object destroyed**`impact(obj.x, obj.y, { particles: { count: 10 } })`

## Trail / Ribbon Effects

A ribbon that follows a moving point. Points fade out over time. Use for sword slashes, projectile trails, movement traces.

```typescript
import { createTrail, updateTrail, drawTrail, clearTrail } from "@arcane/runtime/rendering";

const swordTrail = createTrail({
  maxLength: 20, width: 12,
  color: { r: 1, g: 0.6, b: 0.1, a: 1 },
  endColor: { r: 1, g: 0.2, b: 0, a: 0 },
  maxAge: 0.3, layer: 5, blendMode: "additive",
});

// In onFrame:
updateTrail(swordTrail, swordTipX, swordTipY, dt);
drawTrail(swordTrail);
if (teleported) clearTrail(swordTrail);  // prevent visual artifacts on teleport
```

## Typewriter Dialogue

Progressive character-by-character text reveal. Use for NPC dialogue, story text, tutorial messages.

```typescript
import {
  createTypewriter, updateTypewriter, drawTypewriter,
  skipTypewriter, resetTypewriter, isTypewriterComplete,
  isKeyPressed,
} from "@arcane/runtime/rendering";

const dialogues = ["The dragon approaches...", "Take this sword."];
let dialogueIndex = 0;
const tw = createTypewriter(dialogues[0], { speed: 30, punctuationPause: 0.15 });

// In onFrame:
if (isKeyPressed("Space")) {
  if (isTypewriterComplete(tw)) {
    dialogueIndex++;
    if (dialogueIndex < dialogues.length) resetTypewriter(tw, dialogues[dialogueIndex]);
  } else {
    skipTypewriter(tw);
  }
}
updateTypewriter(tw, dt);
drawTypewriter(tw, 70, 420, { scale: 1, layer: 100, screenSpace: true });
```

## SDF Glow Effects

Use SDF shapes with glow fills and animation helpers for juicy collectibles and pickups:

```typescript
import {
  sdfStar, sdfHeart,
  sdfEntity, clearSdfEntities, flushSdfEntities,
  glow, pulse, spin, breathe, bob,
  LAYERS,
} from "@arcane/runtime/rendering";

// Glowing collectible gem
sdfEntity({
  shape: sdfStar(14, 4, 0.5),
  fill: glow("#00ffaa", 0.2),  // lower intensity = bigger glow
  position: [gemX, gemY + bob(time, 2, 3)],  // gentle bobbing
  rotation: spin(time, 45),                   // slow spin
  scale: pulse(time, 3, 0.9, 1.1),           // pulsing size
  bounds: 50,  // large bounds for glow effect
  layer: LAYERS.ENTITIES + 5,
});

// Pulsing heart pickup
sdfEntity({
  shape: sdfHeart(18),
  fill: glow("#ff3366", 0.15),
  position: [heartX, heartY],
  scale: pulse(time, 2.5, 0.95, 1.15),
  opacity: breathe(time, 2.5, 0.75, 1.0),
  bounds: 70,
  layer: LAYERS.ENTITIES + 5,
});
```

See [sdf.md](sdf.md) for full SDF documentation including shape composition and gradient fills.