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(), andshowThought()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:
- Texture swapping: Replaces the
PIXI.AnimatedSpritetexture array based onANIM_FRAMES[anim] - Horizontal flipping: Sets
sprite.scale.x = -1forleftdirection (reusing therightrow) - Speed adjustment: Tunes
animationSpeedper 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
statusGlyphandglowOnhalo - "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 viaDIRECTION_ROWandANIM_FRAMESconstants - 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →