# How Agent Avatars and Their Animations Are Handled in the Munder Difflin UI

> Discover how Munder Difflin UI uses a state machine and Pixi JS to render agent avatars with animations, responding to real-time events.

- Repository: [Chaitanya Giri/munder-difflin](https://github.com/chaitanyagiri/munder-difflin)
- Tags: ui-design
- Published: 2026-08-28

---

**The Munder Difflin UI renders each agent as a full-body avatar with walk, sit, type, read, and cheer animations, controlled by a state machine in [`Character.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/Character.ts) and rendered through Pixi-JS in [`CharacterSprite.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/CharacterSprite.ts) that responds to real-time hook events from Claude-Code and tmux sessions.**

Agent avatar animation in the Munder Difflin office simulation is built on a clean separation between **state management**, **rendering logic**, and **event-driven updates**. This architecture lets every "employee" avatar react instantly to backend activity—whether that's an agent running a tool, hitting a blocker, or celebrating task completion.

## The Character Class: Avatar State Machine Controller

Located at [[`src/renderer/src/scene/office/Character.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/scene/office/Character.ts)](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/scene/office/Character.ts), the `Character` class serves as the single source of truth for each avatar's behavior and visual state.

### Core Responsibilities

- **State tracking**: Maintains current state (`idle`, `walk`, `type`, `read`), pixel position, tile position, facing **direction**, and **seated status**
- **High-level actions**: Exposes methods like `moveTo()`, `walkToAndThen()`, `sitAtDesk()`, `sitInPlace()`, `cheer()`, and `showThought()` that encapsulate complex multi-step animations
- **Animation delegation**: Calls `this.sprite.setAnimation('walk', this.direction)` (line 185) to command the rendering layer
- **Seated pose handling**: Uses `setSeatedCrop()` to dynamically mask the avatar's legs when sitting (lines 62–85)
- **Visual effects**: Manages overlays including thought bubbles, status glyphs, confetti, cups, and water droplets

The `Character` class never manipulates Pixi-JS directly. Instead, it treats `CharacterSprite` as a dumb rendering slave, issuing semantic commands that the sprite translates into frame updates.

## CharacterSprite: Pixi-JS Rendering and Frame Selection

The [[`src/renderer/src/scene/office/CharacterSprite.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/scene/office/CharacterSprite.ts)](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/scene/office/CharacterSprite.ts) file wraps raw sprite sheet manipulation into a maintainable API.

### Direction and Frame Mapping

```typescript
// Direction maps to sprite sheet row
const DIRECTION_ROW = {
  down: 0,
  up: 1,
  right: 2,
  left: 2  // left rendered as flipped right
} as const;

// Animation states map to frame index lists
const ANIM_FRAMES = {
  idle: [0],
  walk: [0, 1, 2, 3],
  type: [4, 5],
  read: [6, 7]
} as const;

```

The `setAnimation(anim, direction)` method (line 99) performs three critical operations:

1. **Texture swapping**: Replaces the `PIXI.AnimatedSprite` texture array based on `ANIM_FRAMES[anim]`
2. **Horizontal flipping**: Sets `sprite.scale.x = -1` for `left` direction (reusing the `right` row)
3. **Speed adjustment**: Tunes `animationSpeed` per animation type—faster for walking, slower for typing

### Dynamic Cropping for Seated Poses

When an avatar sits, `setSeatedCrop(cropPx)` applies a `PIXI.Graphics` mask to hide leg frames (lines 62–84). This technique lets a single sprite sheet serve both standing and seated poses without duplicate assets.

Global scaling is applied at `CHAR_SCALE = 1.08` (line 24), making avatars slightly larger than their underlying tile grid for visual prominence.

## Hook-Driven State Synchronization

Agent avatars stay synchronized with real backend activity through two specialized React hooks that bridge the renderer to external process events.

### useHive: Claude-Code and Tmux Event Translation

[[`src/renderer/src/hooks/useHive.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/hooks/useHive.ts)](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/hooks/useHive.ts) parses "Hive" hook payloads and invokes `Character` methods:

| Hook Payload Type | Avatar Action |
|-------------------|---------------|
| `PreToolUse` | `moveTo(target)` + `sitAtDesk()` + show "working" thought |
| `PostToolUse` | `cheer()` or idle transition |
| `Notification` | Contextual `showThought()` bubble |

This lets an avatar physically walk to a desk, sit down, and start typing when the underlying agent begins tool execution.

### usePtyParser: Terminal Status Extraction

[[`src/renderer/src/hooks/usePtyParser.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/hooks/usePtyParser.ts)](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/hooks/usePtyParser.ts) watches PTY streams from agent terminals and extracts status symbols:

- **"working"** → Activates `statusGlyph` and `glowOn` halo
- **"blocked"** → Switches glyph to warning indicator
- Spinner patterns → Triggers typing animation state

The parser performs regex matching on terminal output, enabling avatars to reflect low-level process state without explicit API integration.

## Practical Implementation Examples

### Creating and Controlling an Agent Avatar

```typescript
import { Character } from './scene/office/Character';

// Initialize avatar with sprite frames and seat assignment
const meredith = new Character({
  agentId: 'meredith',
  mapRenderer,
  frames,                    // 4 directions × 4 frames from sprite sheet
  seatTile: { x: 12, y: 5 },
  glowColor: 0x00ff00,      // green focus halo for active agent
  seatDirection: 'down',
  onClick: id => console.log(`Clicked ${id}`),
});

```

### Choreographing Multi-Step Actions

```typescript
// Walk to coffee machine, then sit facing right
meredith.moveTo({ x: 14, y: 8 });    // triggers 'walk' animation automatically
// Internal arrival handler calls:
meredith.sitInPlace('right');        // masks legs, switches to idle

```

### Contextual Feedback and Celebration

```typescript
// Show typing indicator in thought bubble
meredith.showThought('Running tests…', 'type');

// Task completion celebration
meredith.cheer();                    // plays cheer animation + particle confetti

```

## Animation State Transitions and Performance

The Munder Difflin avatar system optimizes for **immediate visual feedback** over interpolated tweening:

- **State changes are instant**: `setAnimation()` swaps texture arrays in a single frame
- **No skeletal animation**: Frame-based sprite sheets reduce CPU overhead for multiple simultaneous avatars
- **Direction reuse**: Only three sprite rows stored (down, up, right); left is computed via scale flip
- **Crop over redraw**: Seated poses use runtime masking rather than additional texture variants

This design scales to dozens of agents on screen without GPU bottlenecking, critical for the office simulation's visual density.

## Key Source Files for Avatar System

| File | Purpose | URL |
|------|---------|-----|
| [`Character.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/Character.ts) | State machine, high-level avatar API | [view](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/scene/office/Character.ts) |
| [`CharacterSprite.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/CharacterSprite.ts) | Pixi-JS wrapper, frame selection, cropping | [view](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/scene/office/CharacterSprite.ts) |
| [`ThoughtBubble.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/ThoughtBubble.ts) | Floating text overlays tied to avatar position | [view](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/scene/office/ThoughtBubble.ts) |
| [`useHive.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/useHive.ts) | Hive hook event translator | [view](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/hooks/useHive.ts) |
| [`usePtyParser.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/usePtyParser.ts) | PTY stream parser for status glyphs | [view](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/hooks/usePtyParser.ts) |

## Summary

- **Agent avatars** in Munder Difflin combine a **stateful controller** (`Character`), **sprite renderer** (`CharacterSprite`), and **hook-based event system** (`useHive`, `usePtyParser`)
- **Animation states** (`walk`, `type`, `read`, `idle`) map to sprite sheet rows via `DIRECTION_ROW` and `ANIM_FRAMES` constants
- **Seated poses** use runtime `setSeatedCrop()` masking rather than duplicate assets
- **Real-time synchronization** happens through hook translation of Claude-Code events and terminal PTY output
- **Performance priorities** favor instant state swaps and directional flipping over skeletal animation or texture redundancy

## Frequently Asked Questions

### How does the avatar know which direction to face?

The `Character` class stores a `direction` property (`down`, `up`, `right`, `left`). When `setAnimation()` is called, `CharacterSprite` maps this to a sprite sheet row via `DIRECTION_ROW`. The `left` direction reuses the `right` row with `scale.x = -1` to avoid duplicating texture data.

### What triggers the typing animation specifically?

The `useHive` hook detects `PreToolUse` events and calls `Character.showThought()` with the `'type'` animation key. This sets the sprite to the typing frame sequence (`[4, 5]`). The `usePtyParser` hook can also force typing state when detecting spinner patterns in terminal output.

### Can avatars animate while seated?

Yes. The `setSeatedCrop()` method masks the lower portion of the sprite to hide legs, but the upper body continues animating. The `read` and `type` states are specifically designed for seated execution—trying to play `walk` while seated would visually contradict the cropped pose.

### How are multiple agent avatars kept in sync without frame drops?

Each `Character` instance maintains its own state machine and delegates to a shared `PIXI.Application` ticker. The frame-based approach (single texture swaps rather than bone calculations) keeps per-avatar CPU cost constant regardless of agent count. Direction flipping via scale transform is GPU-accelerated.