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

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 and rendered through Pixi-JS in 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), 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) file wraps raw sprite sheet manipulation into a maintainable API.

Direction and Frame Mapping

// 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) 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) 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

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

// 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

// 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 State machine, high-level avatar API view
CharacterSprite.ts Pixi-JS wrapper, frame selection, cropping view
ThoughtBubble.ts Floating text overlays tied to avatar position view
useHive.ts Hive hook event translator view
usePtyParser.ts PTY stream parser for status glyphs view

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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →