@ludus/commentator
AI commentary pipeline for Ludus games. Classifies events by significance, filters through persona thresholds, generates narration, and delivers via WebSocket or Telegram.
Installation
Section titled “Installation”npm install @ludus/commentatorQuick Example
Section titled “Quick Example”import { LiveCommentaryManager, ORIANA } from '@ludus/commentator';import { GameEventEmitter } from '@ludus/game-engine';
const emitter = new GameEventEmitter();const manager = new LiveCommentaryManager(emitter, ORIANA);
manager.subscribe((commentary) => { console.log(`[${commentary.persona}] ${commentary.text}`);});
// Game runs, events fire, commentary arrives automaticallyPipeline
Section titled “Pipeline”Four stages: classify → filter → narrate → summarize.
CommentaryPipeline
Section titled “CommentaryPipeline”import { CommentaryPipeline, EventClassifier, ContextWindowManager, StubNarrator, ORIANA } from '@ludus/commentator';
const pipeline = new CommentaryPipeline( new StubNarrator(ORIANA), // Or an LLM-backed narrator new EventClassifier(), new ContextWindowManager(), ORIANA,);
const commentary = await pipeline.processEvent(gameEvent);// Commentary | null (null if filtered)EventClassifier
Section titled “EventClassifier”Scores events 0–1 and assigns significance levels:
| Event | Default Score | Level |
|---|---|---|
game_over | 1.0 | critical |
player_eliminated | 0.95 | critical |
game_start | 0.9 | critical |
turn_timeout | 0.6 | high |
action_executed | 0.4 | medium |
invalid_action | 0.2 | low |
turn_skipped | 0.15 | low |
Actions with captures or eliminations get +0.3 boost; critical flag adds +0.2.
import { scoreToLevel, shouldNarrate } from '@ludus/commentator';
scoreToLevel(0.9); // "critical"shouldNarrate(classifiedEvent, 0.4); // true if score >= thresholdContextWindowManager
Section titled “ContextWindowManager”Sliding window with automatic compaction:
import { ContextWindowManager } from '@ludus/commentator';
const ctx = new ContextWindowManager({ maxRecentEvents: 8, maxEstimatedTokens: 2000 });ctx.pushEvent(event);const context = ctx.getContext(); // { recentEvents, eventSummary, turnNumber, estimatedTokens }NarratorProvider
Section titled “NarratorProvider”Interface for generating narration text:
interface NarratorProvider { narrate(event: ClassifiedEvent, context: CommentaryContext): Promise<string>; summarize(result: GameResult, context: CommentaryContext): Promise<GameSummary>; getProviderId(): string;}StubNarrator provides template-based narration for testing (no LLM required).
Personas
Section titled “Personas”Built-in
Section titled “Built-in”| Persona | Game | Tone | Threshold | Verbosity |
|---|---|---|---|---|
| ORIANA | Konquista | Dramatic, imperial | 0.4 | Verbose |
| URBANO | Destreect | Professional, analytical | 0.6 | Balanced |
import { ORIANA, URBANO, PERSONA_PRESETS } from '@ludus/commentator';Custom Personas
Section titled “Custom Personas”import type { CommentatorPersona } from '@ludus/commentator';
const myPersona: CommentatorPersona = { id: 'tacticus', name: 'TACTICUS', gameId: 'chess', description: 'Analytical chess commentator', voice: { tone: 'analytical', vocabulary: ['gambit', 'fork', 'pin'], catchphrases: ['Brilliant move!'] }, biases: { patience: 0.9, creativity: 0.3 }, narrationThreshold: 0.5, verbosity: 'terse', systemPromptTemplate: 'You are {{name}}, a {{tone}} chess commentator...',};PersonaRegistry
Section titled “PersonaRegistry”import { PersonaRegistry } from '@ludus/commentator';
const registry = new PersonaRegistry(); // ORIANA + URBANO auto-registeredregistry.register(myPersona);registry.get('tacticus'); // CommentatorPersonaregistry.list(); // All registered personasLive Commentary
Section titled “Live Commentary”LiveCommentaryManager wires the pipeline into a game’s event emitter:
const manager = new LiveCommentaryManager(emitter, ORIANA);const unsub = manager.subscribe(callback);
manager.getHistory(); // Commentary[] — for late joinersmanager.personaId; // "oriana"manager.dispose(); // Clean upTelegram Delivery
Section titled “Telegram Delivery”TelegramDeliverySink
Section titled “TelegramDeliverySink”Subscribes to LiveCommentaryManager, filters to HIGH + CRITICAL events, and delivers to Telegram subscribers.
import { TelegramDeliverySink, SubscriptionService, InMemorySubscriptionStore, StubTelegramClient } from '@ludus/commentator';
const store = new InMemorySubscriptionStore();const subService = new SubscriptionService(store);const client = new StubTelegramClient(); // Or a real Telegram Bot API client
const sink = new TelegramDeliverySink(manager, client, subService, { gameId: 'game-123', gameType: 'konquista',});
sink.start();// Commentary flows → filtered → formatted → delivered to subscriberssink.signalGameOver(); // Starts 5-min cooldownsink.dispose();Message Formatting
Section titled “Message Formatting”- Critical events: 🔥 Turn N narration text
- High events: ⚔️ Turn N narration text
SubscriptionService
Section titled “SubscriptionService”Handles Telegram bot commands:
const result = await subService.handleCommand('chat-123', 'user-1', { command: 'follow', agentName: 'Caesar' });// { reply: "Subscribed to agent Caesar.", success: true }
const subscribers = await subService.getSubscribersForGame('konquista', ['Caesar', 'Brutus']);Rate Limiting
Section titled “Rate Limiting”import { RateLimiter, DEFAULT_RATE_LIMITS } from '@ludus/commentator';
const limiter = new RateLimiter(); // Uses defaultslimiter.canSend('chat-1', 'game-1'); // true/falselimiter.record('chat-1', 'game-1'); // Record a sendlimiter.remainingForSubscriber('chat-1', 'game-1'); // Remaining allowancelimiter.startCooldown('game-1'); // Post-game cooldownDefault limits: 30 msg/sec global, 20/game per subscriber, 1s batch delay, 5-min post-game cooldown.
// Coreimport type { Commentary, CommentaryCallback, CommentaryContext, ClassifiedEvent, SignificanceLevel, EventCategory, CommentatorPersona, CommentatorVoice, Verbosity, GameSummary, NarratorProvider, PipelineStage } from '@ludus/commentator';
// Telegramimport type { TelegramSubscription, SubscriptionType, SubscriptionStore, TelegramClient, TelegramMessage, TelegramBotCommand, RateLimitConfig, TelegramDeliverySinkConfig, FormattedCommentary, CommandResult } from '@ludus/commentator';