Files
PS_AI_Agent/Docs/gen_convagent_doc.js
j.foucher 48d1543329 Reorganize Blueprint categories under ASTERION namespace, move bAutoStartBehavior to PersonalityComponent, add user documentation
- Bulk rename Category/ClassGroup strings to ASTERION|PS_AI_ConvAgent|... and ASTERION|PS_AI_Behavior|... (284 occurrences, 24 files) so functions/properties/components group cleanly in Blueprint menus
- Move bAutoStartBehavior from AIController to PersonalityComponent so the same AIController class can drive multiple NPC archetypes with per-instance scripted-mode override
- Add PowerPoint user documentation for both plugins in Docs/ with generator scripts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 16:30:48 +02:00

712 lines
56 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// PS_AI_ConvAgent User Documentation Generator
// Palette: Midnight Executive (navy #1E2761, ice blue #CADCFC, white)
const pptxgen = require("pptxgenjs");
const pres = new pptxgen();
pres.layout = "LAYOUT_WIDE"; // 13.333 x 7.5 inch
// ─── Color Palette ─────────────────────────────────
const NAVY = "1E2761";
const NAVY_DARK = "141B45";
const ICE = "CADCFC";
const ICE_DEEP = "7892D6";
const WHITE = "FFFFFF";
const GREY = "3A3F5C";
const LIGHT_GREY= "F5F7FC";
const ACCENT = "F9A825"; // warm gold accent
// ─── Font ──────────────────────────────────────────
const FONT_H = "Calibri";
const FONT_B = "Calibri";
// ─── Master slide backgrounds ──────────────────────
pres.defineSlideMaster({
title: "CONTENT",
background: { color: LIGHT_GREY },
objects: [
{ rect: { x: 0, y: 0, w: 13.333, h: 0.35, fill: { color: NAVY } } },
{ rect: { x: 0, y: 7.15, w: 13.333, h: 0.35, fill: { color: NAVY } } },
{ text: {
text: "PS_AI_ConvAgent — User Documentation",
options: { x: 0.3, y: 7.18, w: 8, h: 0.3, fontSize: 10, color: ICE, fontFace: FONT_B }
} },
{ text: {
text: "ASTERION",
options: { x: 11.5, y: 7.18, w: 1.5, h: 0.3, fontSize: 10, color: ICE, fontFace: FONT_B, align: "right", bold: true }
} },
],
});
// ═══════════════════════════════════════════════════════════════════
// SLIDE 1 — TITLE
// ═══════════════════════════════════════════════════════════════════
let s = pres.addSlide();
s.background = { color: NAVY };
s.addShape(pres.ShapeType.rect, { x: 0, y: 3.3, w: 13.333, h: 0.05, fill: { color: ACCENT } });
s.addText("PS_AI_ConvAgent", { x: 0.6, y: 2.2, w: 12, h: 1.2, fontSize: 60, bold: true, color: WHITE, fontFace: FONT_H });
s.addText("Conversational AI for MetaHumans in Unreal Engine", { x: 0.6, y: 3.5, w: 12, h: 0.6, fontSize: 22, color: ICE, fontFace: FONT_B, italic: true });
s.addText("User Documentation", { x: 0.6, y: 4.4, w: 12, h: 0.5, fontSize: 18, color: ICE_DEEP, fontFace: FONT_B });
s.addText("ElevenLabs • MetaHuman • Listen Server", { x: 0.6, y: 5.2, w: 12, h: 0.4, fontSize: 14, color: ICE_DEEP, fontFace: FONT_B });
s.addText("ASTERION • 2026", { x: 0.6, y: 6.6, w: 12, h: 0.4, fontSize: 12, color: ICE_DEEP, fontFace: FONT_B });
// ═══════════════════════════════════════════════════════════════════
// SLIDE 2 — OVERVIEW
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("Plugin Overview", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("What it does", { x: 0.5, y: 1.7, w: 6, h: 0.4, fontSize: 18, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "Full conversational AI NPC integration for Unreal Engine", options: { bullet: true, fontSize: 14 } },
{ text: "Real-time voice capture, turn-taking, TTS playback", options: { bullet: true, fontSize: 14 } },
{ text: "Emotion-driven MetaHuman animations (face, body, gaze)", options: { bullet: true, fontSize: 14 } },
{ text: "Multiplayer support with speaker arbitration", options: { bullet: true, fontSize: 14 } },
{ text: "Tool system for LLM-triggered game actions", options: { bullet: true, fontSize: 14 } },
], { x: 0.5, y: 2.1, w: 6, h: 2.6, color: GREY, fontFace: FONT_B, paraSpaceAfter: 8 });
s.addShape(pres.ShapeType.roundRect, { x: 7, y: 1.7, w: 5.8, h: 4.8, fill: { color: NAVY }, rectRadius: 0.1 });
s.addText("Primary Use Case", { x: 7.2, y: 1.9, w: 5.4, h: 0.4, fontSize: 16, bold: true, color: ACCENT, fontFace: FONT_H });
s.addText("Interactive, emotionally expressive voice NPCs that listen to players, respond with contextual answers, express emotion through facial animation and body language, and trigger physical actions based on conversation flow.", {
x: 7.2, y: 2.4, w: 5.4, h: 1.7, fontSize: 13, color: ICE, fontFace: FONT_B, italic: true
});
s.addText("Key Differentiators", { x: 7.2, y: 4.2, w: 5.4, h: 0.4, fontSize: 16, bold: true, color: ACCENT, fontFace: FONT_H });
s.addText([
{ text: "Full-duplex Server VAD or push-to-talk", options: { bullet: true, fontSize: 12 } },
{ text: "Adaptive pre-buffering & speculative TTS", options: { bullet: true, fontSize: 12 } },
{ text: "Spectral lip sync + artist-driven poses", options: { bullet: true, fontSize: 12 } },
{ text: "Opus codec + distance LOD for multiplayer", options: { bullet: true, fontSize: 12 } },
], { x: 7.2, y: 4.7, w: 5.4, h: 1.7, color: ICE, fontFace: FONT_B, paraSpaceAfter: 4 });
s.addText("\"Drop components on a MetaHuman, configure a few data assets, and talk.\"", {
x: 0.5, y: 5.3, w: 6, h: 0.6, fontSize: 13, color: NAVY, italic: true, fontFace: FONT_B, bold: true
});
// ═══════════════════════════════════════════════════════════════════
// SLIDE 3 — ARCHITECTURE DIAGRAM
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("Architecture", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("Component chain on a MetaHuman NPC", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
// Input row
const boxY = 2.4;
const boxH = 1.0;
const boxes = [
{ x: 0.5, label: "Microphone\nCapture", sub: "VAD • 16kHz" },
{ x: 2.6, label: "Interaction\nComponent", sub: "Agent select\n(player side)" },
{ x: 4.7, label: "ElevenLabs\nComponent", sub: "WebSocket\nTurn-taking" },
{ x: 6.8, label: "Lip Sync\nComponent", sub: "Spectral\nFFT → visemes" },
{ x: 8.9, label: "Facial Expr.\nComponent", sub: "Emotion\ncurves" },
{ x: 11.0, label: "Body Expr.\nComponent", sub: "Gesture\nanim blend" },
];
boxes.forEach((b, i) => {
s.addShape(pres.ShapeType.roundRect, { x: b.x, y: boxY, w: 2.0, h: boxH, fill: { color: NAVY }, rectRadius: 0.1 });
s.addText(b.label, { x: b.x, y: boxY + 0.1, w: 2.0, h: 0.5, fontSize: 13, bold: true, color: WHITE, align: "center", fontFace: FONT_H });
s.addText(b.sub, { x: b.x, y: boxY + 0.58, w: 2.0, h: 0.4, fontSize: 9, color: ICE, align: "center", fontFace: FONT_B });
});
// Arrows between boxes
for (let i = 0; i < boxes.length - 1; i++) {
const ax = boxes[i].x + 2.0;
s.addShape(pres.ShapeType.line, { x: ax, y: boxY + 0.5, w: 0.1, h: 0, line: { color: ACCENT, width: 2 } });
}
// Gaze row
s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 4.0, w: 2.0, h: boxH, fill: { color: ICE_DEEP }, rectRadius: 0.1 });
s.addText("Gaze\nComponent", { x: 0.5, y: 4.1, w: 2.0, h: 0.5, fontSize: 13, bold: true, color: WHITE, align: "center", fontFace: FONT_H });
s.addText("Eyes • Head\n• Body", { x: 0.5, y: 4.58, w: 2.0, h: 0.4, fontSize: 9, color: WHITE, align: "center", fontFace: FONT_B });
// AnimGraph pipeline
s.addText("→ AnimGraph (Face + Body)", { x: 2.8, y: 4.2, w: 4.5, h: 0.5, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText("FacialExpression → LipSync | Gaze → BodyExpression", { x: 2.8, y: 4.65, w: 8, h: 0.4, fontSize: 12, color: GREY, italic: true, fontFace: FONT_B });
// Project settings layer
s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 5.6, w: 12.3, h: 0.9, fill: { color: LIGHT_GREY }, line: { color: NAVY, width: 1 }, rectRadius: 0.08 });
s.addText("PROJECT SETTINGS — API Key • Server Region • Global Context Prompt", { x: 0.7, y: 5.75, w: 12, h: 0.3, fontSize: 13, bold: true, color: NAVY, fontFace: FONT_H });
s.addText("Data Assets: AgentConfig • EmotionPoseMap • LipSyncPoseMap • BodyPoseMap • ActionSet • Tool", { x: 0.7, y: 6.1, w: 12, h: 0.3, fontSize: 11, color: GREY, fontFace: FONT_B });
// ═══════════════════════════════════════════════════════════════════
// SLIDE 4 — ELEVENLABS COMPONENT
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("ElevenLabs Component", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("The core conversation engine — WebSocket, turn-taking, audio pipeline.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
s.addText("Key Properties", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "AgentConfig — identity, voice, tools", options: { bullet: true, fontSize: 12 } },
{ text: "TurnMode — ServerVAD / Client (push-to-talk)", options: { bullet: true, fontSize: 12 } },
{ text: "MicChunkDurationMs (50250 ms)", options: { bullet: true, fontSize: 12 } },
{ text: "AudioPreBufferMs / bAdaptivePreBuffer", options: { bullet: true, fontSize: 12 } },
{ text: "bAllowInterruption, bPersistentSession", options: { bullet: true, fontSize: 12 } },
{ text: "SoundAttenuation (optional 3D audio)", options: { bullet: true, fontSize: 12 } },
{ text: "AudioLODCullDistance / LipSyncLODDistance", options: { bullet: true, fontSize: 12 } },
], { x: 0.5, y: 2.55, w: 6, h: 3.6, color: GREY, fontFace: FONT_B, paraSpaceAfter: 4 });
s.addText("Key Methods", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "StartConversation() / EndConversation()", options: { bullet: true, fontSize: 12 } },
{ text: "StartListening() / StopListening()", options: { bullet: true, fontSize: 12 } },
{ text: "SendTextMessage() — text without voice", options: { bullet: true, fontSize: 12 } },
{ text: "InterruptAgent() — force stop mid-speech", options: { bullet: true, fontSize: 12 } },
{ text: "ForceDisableConversation(Action) — blend to neutral", options: { bullet: true, fontSize: 12 } },
{ text: "ForceEnableConversation() — re-enable", options: { bullet: true, fontSize: 12 } },
{ text: "FeedExternalAudio() — external mic source", options: { bullet: true, fontSize: 12 } },
], { x: 7, y: 2.55, w: 6, h: 3.6, color: GREY, fontFace: FONT_B, paraSpaceAfter: 4 });
s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 6.2, w: 12.3, h: 0.75, fill: { color: NAVY }, rectRadius: 0.08 });
s.addText("Major events: OnAgentConnected • OnAgentTranscript • OnAgentTextResponse • OnAgentStartedSpeaking • OnAgentEmotionChanged • OnAgentActionRequested • OnActiveSpeakerChanged • OnReadyForAction", { x: 0.7, y: 6.3, w: 12, h: 0.55, fontSize: 11, color: ICE, fontFace: FONT_B, italic: true });
// ═══════════════════════════════════════════════════════════════════
// SLIDE 5 — MICROPHONE CAPTURE
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("Microphone Capture Component", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 34, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("Captures, resamples, and performs local voice activity detection.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
s.addText("Audio Pipeline", { x: 0.5, y: 2.2, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "WASAPI 48kHz stereo float32 input", options: { bullet: true, fontSize: 13 } },
{ text: "Resample to 16kHz mono float", options: { bullet: true, fontSize: 13 } },
{ text: "Convert to int16 LE byte stream", options: { bullet: true, fontSize: 13 } },
{ text: "Delivered via multicast delegate", options: { bullet: true, fontSize: 13 } },
], { x: 0.5, y: 2.6, w: 6, h: 2.4, color: GREY, fontFace: FONT_B, paraSpaceAfter: 6 });
s.addText("Voice Activity Detection", { x: 7, y: 2.2, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "VolumeMultiplier — mic gain", options: { bullet: true, fontSize: 13 } },
{ text: "VoiceActivityThreshold — RMS threshold", options: { bullet: true, fontSize: 13 } },
{ text: "VoiceOnsetTime — debounce on speech start", options: { bullet: true, fontSize: 13 } },
{ text: "VoiceSilenceTime — debounce on end", options: { bullet: true, fontSize: 13 } },
{ text: "OnUserVoiceActivityChanged event", options: { bullet: true, fontSize: 13 } },
], { x: 7, y: 2.6, w: 6, h: 2.4, color: GREY, fontFace: FONT_B, paraSpaceAfter: 6 });
s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 5.4, w: 12.3, h: 1.4, fill: { color: LIGHT_GREY }, line: { color: NAVY, width: 1 }, rectRadius: 0.08 });
s.addText("Public API", { x: 0.7, y: 5.5, w: 12, h: 0.3, fontSize: 13, bold: true, color: NAVY, fontFace: FONT_H });
s.addText("StartCapture() • StopCapture() • IsCapturing()", { x: 0.7, y: 5.85, w: 12, h: 0.3, fontSize: 13, color: GREY, fontFace: "Consolas" });
s.addText("The component auto-opens the default input device. Output is consumed by ElevenLabsComponent (or InteractionComponent, which relays via Server RPC in networked games).", { x: 0.7, y: 6.2, w: 12, h: 0.55, fontSize: 11, color: GREY, italic: true, fontFace: FONT_B });
// ═══════════════════════════════════════════════════════════════════
// SLIDE 6 — LIP SYNC
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("Lip Sync Component", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("Spectral analysis of agent audio → 15 OVR visemes → ARKit blendshapes.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
s.addText("How it works", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "FFT on agent audio stream", options: { bullet: true, fontSize: 13 } },
{ text: "Estimates 15 OVR viseme weights", options: { bullet: true, fontSize: 13 } },
{ text: "Maps to ARKit blendshapes (jawOpen, mouthFunnel…)", options: { bullet: true, fontSize: 13 } },
{ text: "Optional: custom PoseMap with artist-crafted poses", options: { bullet: true, fontSize: 13 } },
{ text: "Thread-safe curve output for AnimGraph", options: { bullet: true, fontSize: 13 } },
], { x: 0.5, y: 2.55, w: 6, h: 3.5, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 });
s.addText("Key Properties", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "LipSyncStrength — overall intensity", options: { bullet: true, fontSize: 13 } },
{ text: "SmoothingSpeed (3565) — viseme interp", options: { bullet: true, fontSize: 13 } },
{ text: "SpeechBlendDuration — fade in/out", options: { bullet: true, fontSize: 13 } },
{ text: "EmotionExpressionBlend (01) — emotion bleed through mouth", options: { bullet: true, fontSize: 13 } },
{ text: "EnvelopeAttackMs / EnvelopeReleaseMs", options: { bullet: true, fontSize: 13 } },
{ text: "PoseMap — optional artist override", options: { bullet: true, fontSize: 13 } },
], { x: 7, y: 2.55, w: 6, h: 3.5, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 });
s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 6.2, w: 12.3, h: 0.75, fill: { color: NAVY }, rectRadius: 0.08 });
s.addText("AnimGraph placement (Face AnimBP): Live Link → FacialExpression → LipSync → mh_arkit_mapping_pose", { x: 0.7, y: 6.3, w: 12, h: 0.55, fontSize: 12, color: ICE, fontFace: FONT_B, italic: true, bold: true });
// ═══════════════════════════════════════════════════════════════════
// SLIDE 7 — FACIAL EXPRESSION
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("Facial Expression Component", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 34, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("Emotion-driven face animation via real-time AnimSequence sampling.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
s.addText("Design", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "Plays AnimSequences in real-time (looping)", options: { bullet: true, fontSize: 13 } },
{ text: "Extracts emotion curves (eyes, brows, cheeks, mouth mood)", options: { bullet: true, fontSize: 13 } },
{ text: "Injects into AnimGraph via FacialExpression AnimNode", options: { bullet: true, fontSize: 13 } },
{ text: "Lip sync overrides mouth area during speech", options: { bullet: true, fontSize: 13 } },
{ text: "3 intensity levels: Normal / Medium / Extreme", options: { bullet: true, fontSize: 13 } },
{ text: "Crossfades between emotions", options: { bullet: true, fontSize: 13 } },
], { x: 0.5, y: 2.55, w: 6, h: 3.5, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 });
s.addText("Properties", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "EmotionPoseMap — data asset of AnimSequences", options: { bullet: true, fontSize: 13 } },
{ text: "ActivationBlendDuration (default 0.5s)", options: { bullet: true, fontSize: 13 } },
{ text: "EmotionBlendDuration — crossfade time", options: { bullet: true, fontSize: 13 } },
{ text: "bActive — auto-managed by conversation", options: { bullet: true, fontSize: 13 } },
], { x: 7, y: 2.55, w: 6, h: 2.4, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 });
s.addShape(pres.ShapeType.roundRect, { x: 7, y: 5.0, w: 5.8, h: 1.5, fill: { color: LIGHT_GREY }, line: { color: NAVY, width: 1 }, rectRadius: 0.08 });
s.addText("Emotions covered", { x: 7.15, y: 5.1, w: 5.6, h: 0.3, fontSize: 13, bold: true, color: NAVY, fontFace: FONT_H });
s.addText("Joy • Sadness • Anger • Fear • Surprise • Disgust • Neutral", { x: 7.15, y: 5.45, w: 5.6, h: 0.9, fontSize: 12, color: GREY, italic: true, fontFace: FONT_B });
// ═══════════════════════════════════════════════════════════════════
// SLIDE 8 — BODY EXPRESSION
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("Body Expression Component", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 34, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("Upper-body gestures and posture driven by emotion + speaking state.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
s.addText("Animation lists per emotion", { x: 0.5, y: 2.15, w: 6.3, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
const animLists = [
{ name: "Idle", desc: "Played while listening (not speaking)" },
{ name: "Normal", desc: "Low-intensity speaking anims" },
{ name: "Medium", desc: "Medium-intensity speaking" },
{ name: "Extreme", desc: "High-intensity speaking" },
];
animLists.forEach((a, i) => {
const y = 2.7 + i * 0.75;
s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: y, w: 1.5, h: 0.55, fill: { color: NAVY }, rectRadius: 0.05 });
s.addText(a.name, { x: 0.5, y: y, w: 1.5, h: 0.55, fontSize: 13, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: FONT_H });
s.addText(a.desc, { x: 2.15, y: y + 0.05, w: 4.6, h: 0.45, fontSize: 12, color: GREY, valign: "middle", fontFace: FONT_B });
});
s.addText("Key Properties", { x: 7.2, y: 2.15, w: 5.8, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "BodyPoseMap — emotion → anim lists", options: { bullet: true, fontSize: 13 } },
{ text: "ActivationBlendDuration (0.5s)", options: { bullet: true, fontSize: 13 } },
{ text: "EmotionBlendDuration (1.0s)", options: { bullet: true, fontSize: 13 } },
{ text: "BlendWeight (01)", options: { bullet: true, fontSize: 13 } },
], { x: 7.2, y: 2.55, w: 5.8, h: 2, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 });
s.addText("AnimNode behaviour", { x: 7.2, y: 4.7, w: 5.8, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "Override mode, per-bone mask", options: { bullet: true, fontSize: 12 } },
{ text: "Default BlendRootBone = spine_02", options: { bullet: true, fontSize: 12 } },
{ text: "Excludes neck_01 (Gaze handles it)", options: { bullet: true, fontSize: 12 } },
{ text: "Lower body passes through upstream", options: { bullet: true, fontSize: 12 } },
], { x: 7.2, y: 5.1, w: 5.8, h: 2, color: GREY, fontFace: FONT_B, paraSpaceAfter: 3 });
// ═══════════════════════════════════════════════════════════════════
// SLIDE 9 — GAZE
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("Gaze Component", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("Multi-layer look-at: body • head • eyes.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
const layers = [
{ name: "BODY", ratio: "full 360°", desc: "Actor yaw — follows target around the character" },
{ name: "HEAD", ratio: "limited", desc: "Head bone yaw + pitch, clamped" },
{ name: "EYES", ratio: "limited", desc: "ARKit eye curves, fine targeting" },
];
layers.forEach((l, i) => {
const y = 2.3 + i * 1.1;
s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: y, w: 2.2, h: 0.9, fill: { color: NAVY }, rectRadius: 0.08 });
s.addText(l.name, { x: 0.5, y: y + 0.05, w: 2.2, h: 0.4, fontSize: 18, bold: true, color: WHITE, align: "center", fontFace: FONT_H });
s.addText(l.ratio, { x: 0.5, y: y + 0.48, w: 2.2, h: 0.3, fontSize: 11, color: ACCENT, align: "center", italic: true, fontFace: FONT_B });
s.addText(l.desc, { x: 3.0, y: y + 0.15, w: 5.0, h: 0.7, fontSize: 13, color: GREY, valign: "middle", fontFace: FONT_B });
});
s.addText("Key Properties", { x: 8.5, y: 2.15, w: 4.5, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "TargetActor — who to look at", options: { bullet: true, fontSize: 12 } },
{ text: "MaxHeadYaw / MaxHeadPitch", options: { bullet: true, fontSize: 12 } },
{ text: "MaxEyeHorizontal / MaxEyeVertical", options: { bullet: true, fontSize: 12 } },
{ text: "BodyInterpSpeed / HeadInterpSpeed / EyeInterpSpeed", options: { bullet: true, fontSize: 12 } },
{ text: "HeadAnimationCompensation (0.9)", options: { bullet: true, fontSize: 12 } },
{ text: "EyeAnimationCompensation (0.6)", options: { bullet: true, fontSize: 12 } },
{ text: "BodyDriftCompensation (0.8)", options: { bullet: true, fontSize: 12 } },
{ text: "NeckBoneChain — multi-bone distribution", options: { bullet: true, fontSize: 12 } },
], { x: 8.5, y: 2.55, w: 4.5, h: 4, color: GREY, fontFace: FONT_B, paraSpaceAfter: 3 });
// ═══════════════════════════════════════════════════════════════════
// SLIDE 10 — INTERACTION COMPONENT
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("Interaction Component (Player)", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 32, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("Placed on the player pawn — selects NPCs, routes mic, manages gaze.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
s.addText("Selection Logic", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "Proximity + view cone detection", options: { bullet: true, fontSize: 13 } },
{ text: "MaxInteractionDistance (300 cm default)", options: { bullet: true, fontSize: 13 } },
{ text: "ViewConeHalfAngle (45° default)", options: { bullet: true, fontSize: 13 } },
{ text: "SelectionStickyAngle — hysteresis", options: { bullet: true, fontSize: 13 } },
{ text: "ConversationSwitchDelay — anti-flicker", options: { bullet: true, fontSize: 13 } },
], { x: 0.5, y: 2.55, w: 6, h: 3, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 });
s.addText("Auto-management", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "bAutoStartConversation — open WS on select", options: { bullet: true, fontSize: 13 } },
{ text: "bAutoManageListening — mic open/close", options: { bullet: true, fontSize: 13 } },
{ text: "bAutoManageGaze — set agent gaze to player", options: { bullet: true, fontSize: 13 } },
{ text: "bAutoPassiveGaze — out-of-range passive tracking", options: { bullet: true, fontSize: 13 } },
], { x: 7, y: 2.55, w: 6, h: 2.5, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 });
s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 5.8, w: 12.3, h: 1.2, fill: { color: NAVY }, rectRadius: 0.08 });
s.addText("Events: OnAgentSelected • OnAgentDeselected • OnNoAgentInRange", { x: 0.7, y: 5.95, w: 12, h: 0.3, fontSize: 13, bold: true, color: ACCENT, fontFace: FONT_H });
s.addText("Methods: GetSelectedAgent() • ForceSelectAgent() • StartConversationWithSelectedAgent() • SendTextToSelectedAgent() • ClearSelection()", { x: 0.7, y: 6.3, w: 12, h: 0.55, fontSize: 11, color: ICE, fontFace: FONT_B });
// ═══════════════════════════════════════════════════════════════════
// SLIDE 11 — DATA ASSETS OVERVIEW
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("Data Assets", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("Reusable configuration assets for agent identity, animations, and tools.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
const dataAssets = [
{ name: "AgentConfig", desc: "Agent identity, voice, LLM model, tools", color: NAVY },
{ name: "EmotionPoseMap", desc: "Emotion → facial AnimSequences (3 levels)", color: NAVY },
{ name: "LipSyncPoseMap", desc: "15 OVR visemes → mouth poses", color: NAVY },
{ name: "BodyPoseMap", desc: "Emotion → body gesture anim lists", color: NAVY },
{ name: "ActionSet", desc: "Actions the agent can trigger", color: ICE_DEEP },
{ name: "Tool", desc: "Custom LLM tools with parameters", color: ICE_DEEP },
];
dataAssets.forEach((d, i) => {
const col = i % 2;
const row = Math.floor(i / 2);
const x = 0.5 + col * 6.3;
const y = 2.2 + row * 1.5;
s.addShape(pres.ShapeType.roundRect, { x: x, y: y, w: 6.0, h: 1.3, fill: { color: d.color }, rectRadius: 0.1 });
s.addText(d.name, { x: x + 0.2, y: y + 0.2, w: 5.6, h: 0.4, fontSize: 18, bold: true, color: ACCENT, fontFace: FONT_H });
s.addText(d.desc, { x: x + 0.2, y: y + 0.65, w: 5.6, h: 0.6, fontSize: 12, color: ICE, italic: true, fontFace: FONT_B });
});
// ═══════════════════════════════════════════════════════════════════
// SLIDE 12 — AGENT CONFIG DETAILS
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("AgentConfig (ElevenLabs)", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 34, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("Complete agent definition — editable in Content Browser with API buttons.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
const configCats = [
{ title: "Identity", items: ["AgentID", "AgentName"] },
{ title: "Voice", items: ["VoiceID / VoiceName", "TTSModelID", "Stability • Similarity • Speed"] },
{ title: "LLM", items: ["LLMModel (gemini-2.5-flash default)", "Language (en default)"] },
{ title: "Behavior", items: ["CharacterPrompt (personality)", "FirstMessage (greeting)", "TurnTimeout, MaxTurns"] },
{ title: "Latency", items: ["TurnEagerness (Eager/Normal/Patient)", "bSpeculativeTurn"] },
{ title: "Tools", items: ["Tools[] array of Tool assets", "ActionSet integration"] },
];
configCats.forEach((c, i) => {
const col = i % 3;
const row = Math.floor(i / 3);
const x = 0.5 + col * 4.15;
const y = 2.2 + row * 2.15;
s.addShape(pres.ShapeType.roundRect, { x: x, y: y, w: 3.95, h: 1.95, fill: { color: LIGHT_GREY }, line: { color: NAVY, width: 1 }, rectRadius: 0.08 });
s.addText(c.title, { x: x + 0.15, y: y + 0.1, w: 3.65, h: 0.35, fontSize: 14, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: x + 0.15, y: y + 0.45, w: 0.35, h: 0.04, fill: { color: ACCENT } });
s.addText(c.items.map(t => ({ text: t, options: { bullet: true, fontSize: 11 } })),
{ x: x + 0.15, y: y + 0.55, w: 3.65, h: 1.3, color: GREY, fontFace: FONT_B, paraSpaceAfter: 2 });
});
s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 6.55, w: 12.3, h: 0.45, fill: { color: NAVY }, rectRadius: 0.05 });
s.addText("Editor: Voice picker • Model picker • LLM picker • Create / Update / Fetch Agent buttons", { x: 0.7, y: 6.6, w: 12, h: 0.35, fontSize: 11, color: ICE, fontFace: FONT_B, italic: true, bold: true });
// ═══════════════════════════════════════════════════════════════════
// SLIDE 13 — POSE MAPS
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("Pose Maps", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("Three pose-map data assets map abstract states → AnimSequences.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
// Emotion
s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 2.15, w: 4.0, h: 4.5, fill: { color: NAVY }, rectRadius: 0.1 });
s.addText("EmotionPoseMap", { x: 0.6, y: 2.3, w: 3.8, h: 0.4, fontSize: 17, bold: true, color: ACCENT, fontFace: FONT_H });
s.addText("Emotion → facial AnimSequences\n(3 intensity levels per emotion)", { x: 0.6, y: 2.7, w: 3.8, h: 0.8, fontSize: 11, color: ICE, italic: true, fontFace: FONT_B });
s.addText([
{ text: "Joy (N / M / E)", options: { bullet: true, fontSize: 11 } },
{ text: "Sadness (N / M / E)", options: { bullet: true, fontSize: 11 } },
{ text: "Anger (N / M / E)", options: { bullet: true, fontSize: 11 } },
{ text: "Fear (N / M / E)", options: { bullet: true, fontSize: 11 } },
{ text: "Surprise (N / M / E)", options: { bullet: true, fontSize: 11 } },
{ text: "Disgust (N / M / E)", options: { bullet: true, fontSize: 11 } },
{ text: "Neutral (N / M / E)", options: { bullet: true, fontSize: 11 } },
], { x: 0.7, y: 3.7, w: 3.7, h: 2.8, color: ICE, fontFace: FONT_B, paraSpaceAfter: 3 });
// LipSync
s.addShape(pres.ShapeType.roundRect, { x: 4.7, y: 2.15, w: 4.0, h: 4.5, fill: { color: NAVY }, rectRadius: 0.1 });
s.addText("LipSyncPoseMap", { x: 4.8, y: 2.3, w: 3.8, h: 0.4, fontSize: 17, bold: true, color: ACCENT, fontFace: FONT_H });
s.addText("15 OVR visemes →\nphoneme AnimSequences", { x: 4.8, y: 2.7, w: 3.8, h: 0.7, fontSize: 11, color: ICE, italic: true, fontFace: FONT_B });
s.addText([
{ text: "sil • PP • FF • TH • DD", options: { bullet: true, fontSize: 11 } },
{ text: "kk • CH • SS • nn • RR", options: { bullet: true, fontSize: 11 } },
{ text: "aa • E • ih • oh • ou", options: { bullet: true, fontSize: 11 } },
{ text: "MetaHuman MHF_* recommended", options: { bullet: true, fontSize: 11 } },
{ text: "Shareable across characters", options: { bullet: true, fontSize: 11 } },
], { x: 4.9, y: 3.5, w: 3.7, h: 2.9, color: ICE, fontFace: FONT_B, paraSpaceAfter: 4 });
// Body
s.addShape(pres.ShapeType.roundRect, { x: 8.9, y: 2.15, w: 4.0, h: 4.5, fill: { color: NAVY }, rectRadius: 0.1 });
s.addText("BodyPoseMap", { x: 9.0, y: 2.3, w: 3.8, h: 0.4, fontSize: 17, bold: true, color: ACCENT, fontFace: FONT_H });
s.addText("Emotion → body anim lists\n(Idle / N / M / E per emotion)", { x: 9.0, y: 2.7, w: 3.8, h: 0.8, fontSize: 11, color: ICE, italic: true, fontFace: FONT_B });
s.addText([
{ text: "Idle: listening animations", options: { bullet: true, fontSize: 11 } },
{ text: "Normal: light speaking", options: { bullet: true, fontSize: 11 } },
{ text: "Medium: expressive speaking", options: { bullet: true, fontSize: 11 } },
{ text: "Extreme: emphatic speaking", options: { bullet: true, fontSize: 11 } },
{ text: "Random selection per loop", options: { bullet: true, fontSize: 11 } },
{ text: "Continuous variety", options: { bullet: true, fontSize: 11 } },
], { x: 9.0, y: 3.7, w: 3.8, h: 2.8, color: ICE, fontFace: FONT_B, paraSpaceAfter: 3 });
// ═══════════════════════════════════════════════════════════════════
// SLIDE 14 — TOOLS & ACTIONSETS
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("Tools & ActionSets", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("LLM-triggered actions that bridge conversation → gameplay.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
s.addText("Tool asset", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "ToolName — snake_case identifier", options: { bullet: true, fontSize: 13 } },
{ text: "ToolDescription — when LLM should use it", options: { bullet: true, fontSize: 13 } },
{ text: "PromptFragment — appended system prompt", options: { bullet: true, fontSize: 13 } },
{ text: "Parameters[] — typed LLM arguments", options: { bullet: true, fontSize: 13 } },
{ text: "ToolID auto-populated on Create", options: { bullet: true, fontSize: 13 } },
], { x: 0.5, y: 2.55, w: 6, h: 2.8, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 });
s.addText("Built-in Tools", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "set_emotion(emotion, intensity)", options: { bullet: true, fontSize: 13 } },
{ text: "perform_action(action) — from ActionSet", options: { bullet: true, fontSize: 13 } },
{ text: "Custom tools — any parameters", options: { bullet: true, fontSize: 13 } },
], { x: 7, y: 2.55, w: 6, h: 2, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 });
s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 5.4, w: 12.3, h: 1.6, fill: { color: LIGHT_GREY }, line: { color: NAVY, width: 1 }, rectRadius: 0.08 });
s.addText("Action flow", { x: 0.7, y: 5.5, w: 12, h: 0.3, fontSize: 14, bold: true, color: NAVY, fontFace: FONT_H });
s.addText("1. LLM decides to call a tool (e.g. perform_action(\"flee\"))\n2. Plugin fires OnAgentActionRequested(\"flee\")\n3. Game calls ForceDisableConversation(\"flee\") → animations blend to neutral\n4. OnReadyForAction(\"flee\") fires → play gameplay montage\n5. Game calls ForceEnableConversation() when done", { x: 0.7, y: 5.85, w: 12, h: 1.1, fontSize: 11, color: GREY, fontFace: "Consolas" });
// ═══════════════════════════════════════════════════════════════════
// SLIDE 15 — ANIMGRAPH INTEGRATION
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("AnimGraph Integration", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 34, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("Four AnimNodes auto-discover components — no manual wiring.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
// Face AnimBP chain
s.addText("Face AnimBP", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
const faceChain = [
{ label: "Live Link", color: ICE_DEEP },
{ label: "Facial\nExpression", color: "E88B3E" },
{ label: "Gaze\n(eyes only)", color: "5E7AC7" },
{ label: "Lip Sync", color: "3EB086" },
{ label: "mh_arkit\n_mapping", color: GREY },
];
faceChain.forEach((n, i) => {
const x = 0.5 + i * 2.55;
s.addShape(pres.ShapeType.roundRect, { x: x, y: 2.65, w: 2.3, h: 1.0, fill: { color: n.color }, rectRadius: 0.1 });
s.addText(n.label, { x: x, y: 2.75, w: 2.3, h: 0.8, fontSize: 12, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: FONT_H });
});
// Body AnimBP chain
s.addText("Body AnimBP", { x: 0.5, y: 4.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
const bodyChain = [
{ label: "Locomotion\n/ Idle", color: ICE_DEEP },
{ label: "Gaze\n(head rot)", color: "5E7AC7" },
{ label: "Body\nExpression", color: "B85042" },
{ label: "Output Pose", color: GREY },
];
bodyChain.forEach((n, i) => {
const x = 0.5 + i * 3.2;
s.addShape(pres.ShapeType.roundRect, { x: x, y: 4.65, w: 2.9, h: 1.0, fill: { color: n.color }, rectRadius: 0.1 });
s.addText(n.label, { x: x, y: 4.75, w: 2.9, h: 0.8, fontSize: 12, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: FONT_H });
});
s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: 6.15, w: 12.3, h: 0.85, fill: { color: NAVY }, rectRadius: 0.08 });
s.addText("Why this order?", { x: 0.7, y: 6.2, w: 12, h: 0.3, fontSize: 13, bold: true, color: ACCENT, fontFace: FONT_H });
s.addText("Emotion curves set mood (brows, eyes, cheeks). Lip sync overrides mouth during speech. Gaze head rotation runs before body expression so it doesn't fight arms.", { x: 0.7, y: 6.55, w: 12, h: 0.4, fontSize: 11, color: ICE, italic: true, fontFace: FONT_B });
// ═══════════════════════════════════════════════════════════════════
// SLIDE 16 — PROJECT SETTINGS
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("Project Settings", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("Project Settings → Plugins → PS AI ConvAgent - ElevenLabs", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
const settings = [
{ name: "API_Key", desc: "ElevenLabs API key — required for authentication" },
{ name: "ServerRegion", desc: "Global / US / EU / India" },
{ name: "CustomWebSocketURL", desc: "Override WebSocket URL (advanced)" },
{ name: "bVerboseLogging", desc: "Verbose WebSocket logging (development)" },
{ name: "GlobalContextPrompt", desc: "Shared context injected as dynamic variables for all agents" },
];
settings.forEach((setting, i) => {
const y = 2.3 + i * 0.85;
s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: y, w: 3.5, h: 0.65, fill: { color: NAVY }, rectRadius: 0.05 });
s.addText(setting.name, { x: 0.5, y: y, w: 3.5, h: 0.65, fontSize: 13, bold: true, color: ACCENT, align: "center", valign: "middle", fontFace: "Consolas" });
s.addText(setting.desc, { x: 4.2, y: y + 0.05, w: 8.6, h: 0.55, fontSize: 12, color: GREY, valign: "middle", fontFace: FONT_B });
});
// ═══════════════════════════════════════════════════════════════════
// SLIDE 17 — SETUP WORKFLOW
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("Setup Workflow", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("From zero to a talking MetaHuman in 5 steps.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
const steps = [
{ num: "1", title: "Project Settings", desc: "Enter API_Key, pick ServerRegion." },
{ num: "2", title: "Data Assets", desc: "Create AgentConfig, EmotionPoseMap, LipSyncPoseMap, BodyPoseMap." },
{ num: "3", title: "NPC Components", desc: "Add ElevenLabs + Mic + LipSync + FacialExpr + BodyExpr + Gaze on the MetaHuman." },
{ num: "4", title: "AnimGraphs", desc: "Wire AnimNodes in Face + Body AnimBPs. Components auto-discover." },
{ num: "5", title: "Player Pawn", desc: "Add InteractionComponent. Bind selection events to UI. Play." },
];
steps.forEach((step, i) => {
const y = 2.25 + i * 0.9;
s.addShape(pres.ShapeType.ellipse, { x: 0.5, y: y, w: 0.8, h: 0.8, fill: { color: ACCENT } });
s.addText(step.num, { x: 0.5, y: y, w: 0.8, h: 0.8, fontSize: 28, bold: true, color: NAVY, align: "center", valign: "middle", fontFace: FONT_H });
s.addShape(pres.ShapeType.roundRect, { x: 1.6, y: y, w: 11.2, h: 0.8, fill: { color: LIGHT_GREY }, line: { color: NAVY, width: 1 }, rectRadius: 0.05 });
s.addText(step.title, { x: 1.8, y: y + 0.1, w: 3.5, h: 0.3, fontSize: 14, bold: true, color: NAVY, fontFace: FONT_H });
s.addText(step.desc, { x: 1.8, y: y + 0.4, w: 11, h: 0.35, fontSize: 11, color: GREY, italic: true, fontFace: FONT_B });
});
// ═══════════════════════════════════════════════════════════════════
// SLIDE 18 — NETWORKING
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("Networking & Multiplayer", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 34, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("Listen Server model — server owns WebSocket, clients relay via RPCs.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
s.addText("Audio Pipeline", { x: 0.5, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "Client mic → Server RPC (Opus-encoded)", options: { bullet: true, fontSize: 13 } },
{ text: "Server forwards to ElevenLabs WebSocket", options: { bullet: true, fontSize: 13 } },
{ text: "Agent audio → Multicast RPC to all clients", options: { bullet: true, fontSize: 13 } },
{ text: "Clients decode Opus → procedural sound wave", options: { bullet: true, fontSize: 13 } },
], { x: 0.5, y: 2.55, w: 6, h: 2.4, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 });
s.addText("Speaker Arbitration", { x: 7, y: 2.15, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "SpeakerSwitchHysteresis — min silence before switching", options: { bullet: true, fontSize: 13 } },
{ text: "SpeakerIdleTimeout — clear active speaker", options: { bullet: true, fontSize: 13 } },
{ text: "NetActiveSpeakerPawn replicated for gaze", options: { bullet: true, fontSize: 13 } },
{ text: "Only active speaker's audio forwarded to LLM", options: { bullet: true, fontSize: 13 } },
], { x: 7, y: 2.55, w: 6, h: 2.4, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 });
s.addText("Level of Detail (LOD)", { x: 0.5, y: 5.1, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "AudioLODCullDistance — skip audio at distance", options: { bullet: true, fontSize: 13 } },
{ text: "LipSyncLODDistance — skip lip sync at distance", options: { bullet: true, fontSize: 13 } },
{ text: "Active speaker always full quality", options: { bullet: true, fontSize: 13 } },
], { x: 0.5, y: 5.5, w: 6, h: 1.5, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 });
s.addText("Replicated State", { x: 7, y: 5.1, w: 6, h: 0.4, fontSize: 16, bold: true, color: NAVY, fontFace: FONT_H });
s.addText([
{ text: "bNetIsConversing", options: { bullet: true, fontSize: 13 } },
{ text: "NetConnectedPawns[]", options: { bullet: true, fontSize: 13 } },
{ text: "CurrentEmotion + CurrentEmotionIntensity", options: { bullet: true, fontSize: 13 } },
], { x: 7, y: 5.5, w: 6, h: 1.5, color: GREY, fontFace: FONT_B, paraSpaceAfter: 5 });
// ═══════════════════════════════════════════════════════════════════
// SLIDE 19 — FORCE DISABLE FLOW
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("ForceDisableConversation Flow", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 30, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("Clean handoff between conversation and gameplay actions.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
const flow = [
{ step: "LLM Tool Call", desc: "perform_action(\"flee\")" },
{ step: "OnAgentActionRequested", desc: "Event fires on ElevenLabs component" },
{ step: "ForceDisableConversation", desc: "Blend out face/body/gaze animations" },
{ step: "OnReadyForAction", desc: "All components have reached neutral" },
{ step: "Play Gameplay", desc: "Montage / behavior switch" },
{ step: "ForceEnableConversation", desc: "Resume conversation when done" },
];
flow.forEach((f, i) => {
const y = 2.2 + i * 0.75;
s.addShape(pres.ShapeType.roundRect, { x: 0.5, y: y, w: 4, h: 0.6, fill: { color: NAVY }, rectRadius: 0.08 });
s.addText(f.step, { x: 0.5, y: y, w: 4, h: 0.6, fontSize: 13, bold: true, color: WHITE, align: "center", valign: "middle", fontFace: "Consolas" });
s.addText("→", { x: 4.6, y: y + 0.05, w: 0.5, h: 0.5, fontSize: 22, color: ACCENT, align: "center", valign: "middle", bold: true, fontFace: FONT_H });
s.addText(f.desc, { x: 5.2, y: y + 0.05, w: 7.6, h: 0.5, fontSize: 13, color: GREY, italic: true, valign: "middle", fontFace: FONT_B });
});
// ═══════════════════════════════════════════════════════════════════
// SLIDE 20 — EVENTS CHEAT SHEET
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide({ masterName: "CONTENT" });
s.addText("Events Cheat Sheet", { x: 0.5, y: 0.6, w: 12, h: 0.8, fontSize: 36, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: 0.5, y: 1.45, w: 0.6, h: 0.06, fill: { color: ACCENT } });
s.addText("Blueprint-assignable delegates on ElevenLabs component.", { x: 0.5, y: 1.65, w: 12, h: 0.4, fontSize: 14, color: GREY, italic: true, fontFace: FONT_B });
const eventCols = [
{
title: "Connection",
items: ["OnAgentConnected", "OnAgentDisconnected", "OnAgentError"]
},
{
title: "Dialogue",
items: ["OnAgentTranscript", "OnAgentTextResponse", "OnAgentPartialResponse", "OnAgentStartedGenerating"]
},
{
title: "Audio",
items: ["OnAgentStartedSpeaking", "OnAudioPlaybackStarted", "OnAgentStoppedSpeaking", "OnAgentInterrupted"]
},
{
title: "State / Actions",
items: ["OnAgentEmotionChanged", "OnAgentActionRequested", "OnAgentClientToolCall", "OnActiveSpeakerChanged", "OnReadyForAction", "OnAgentResponseTimeout"]
},
];
eventCols.forEach((c, i) => {
const col = i % 2;
const row = Math.floor(i / 2);
const x = 0.5 + col * 6.3;
const y = 2.15 + row * 2.45;
s.addShape(pres.ShapeType.roundRect, { x: x, y: y, w: 6.0, h: 2.25, fill: { color: LIGHT_GREY }, line: { color: NAVY, width: 1 }, rectRadius: 0.08 });
s.addText(c.title, { x: x + 0.2, y: y + 0.1, w: 5.6, h: 0.3, fontSize: 14, bold: true, color: NAVY, fontFace: FONT_H });
s.addShape(pres.ShapeType.rect, { x: x + 0.2, y: y + 0.42, w: 0.35, h: 0.04, fill: { color: ACCENT } });
s.addText(c.items.map(t => ({ text: t, options: { bullet: true, fontSize: 11 } })),
{ x: x + 0.2, y: y + 0.52, w: 5.6, h: 1.7, color: GREY, fontFace: "Consolas", paraSpaceAfter: 2 });
});
// ═══════════════════════════════════════════════════════════════════
// SLIDE 21 — CLOSING / SUMMARY
// ═══════════════════════════════════════════════════════════════════
s = pres.addSlide();
s.background = { color: NAVY };
s.addShape(pres.ShapeType.rect, { x: 0, y: 2.5, w: 13.333, h: 0.05, fill: { color: ACCENT } });
s.addText("Summary", { x: 0.6, y: 0.8, w: 12, h: 1.2, fontSize: 54, bold: true, color: WHITE, fontFace: FONT_H });
s.addText("A complete conversational AI stack for Unreal Engine.", { x: 0.6, y: 2.0, w: 12, h: 0.5, fontSize: 18, color: ICE, italic: true, fontFace: FONT_B });
const summary = [
{ h: "Real-time", t: "Spectral lip sync, speculative TTS, adaptive pre-buffering" },
{ h: "Expressive", t: "Emotion-driven face/body animation via AnimSequences" },
{ h: "Multiplayer", t: "Listen Server + Opus codec + distance LOD + speaker arbitration" },
{ h: "Data-driven", t: "Reusable PoseMaps, AgentConfigs, Tools, ActionSets" },
{ h: "Bridgeable", t: "Behavior plugin gaze bridge, ForceDisable handoff for gameplay" },
{ h: "BP-friendly", t: "All components blueprintable, rich event system" },
];
summary.forEach((item, i) => {
const col = i % 2;
const row = Math.floor(i / 2);
const x = 0.6 + col * 6.2;
const y = 3.0 + row * 1.4;
s.addShape(pres.ShapeType.roundRect, { x: x, y: y, w: 5.9, h: 1.2, fill: { color: NAVY_DARK }, line: { color: ACCENT, width: 1 }, rectRadius: 0.1 });
s.addText(item.h, { x: x + 0.2, y: y + 0.1, w: 5.5, h: 0.4, fontSize: 18, bold: true, color: ACCENT, fontFace: FONT_H });
s.addText(item.t, { x: x + 0.2, y: y + 0.55, w: 5.5, h: 0.6, fontSize: 12, color: ICE, italic: true, fontFace: FONT_B });
});
s.addText("ASTERION • PS_AI_ConvAgent • 2026", { x: 0.6, y: 6.9, w: 12, h: 0.4, fontSize: 12, color: ICE_DEEP, fontFace: FONT_B });
// ═══════════════════════════════════════════════════════════════════
// SAVE
// ═══════════════════════════════════════════════════════════════════
pres.writeFile({ fileName: "PS_AI_ConvAgent_Documentation.pptx" })
.then(name => console.log(`Generated: ${name}`))
.catch(err => { console.error(err); process.exit(1); });