Editor toolset wrapper declaring MCP tools that generate polish/game-feel scaffolding (camera shakes, flicker lights, HUDs, dialogue, lip-sync, floating combat text, cutscenes, nametags, etc.). Each public method forwards parameters to McpGate.Run with the corresponding tool name.
// AUTO-GENERATED by scripts/emit-mcp-wrappers.mjs — DO NOT EDIT.
// Regenerate: node scripts/extract-manifest.mjs && node scripts/emit-mcp-wrappers.mjs
// Source of truth: sbox-mcp-server/src/tools/ (zod schemas) → scripts/tools-manifest.json
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Editor.Mcp;
/// <summary>
/// Generate game-feel and presentation components: camera shake, flickering lights, floating combat
/// text, combo meters, nametags, world-panel UIs, cutscene directors, and dialogue systems.
/// </summary>
[McpToolset( "bridge_scaffold_polish", "Generate game-feel and presentation components: camera shake, flickering lights, floating combat text, combo meters, nametags, world-panel UIs, cutscene directors, and dialogue systems." )]
public static class BridgeScaffoldPolishTools
{
/// <summary>
/// Generate a light-flicker animator and optionally attach it to an existing light GameObject by
/// GUID. Five presets: Candle (soft organic sway), Fluorescent (mostly steady with random dips),
/// Faulty (hard on/off cuts), Pulse (slow sine breathing), Lightning (dim baseline with rare bright
/// flashes). Modulates the sibling Light component's LightColor around the color it found on enable
/// (works on PointLight / SpotLight / DirectionalLight) and restores it exactly on disable;
/// intensity 0..1 sets flicker depth, speed scales the whole pattern. The single biggest atmosphere
/// win per call for horror/night scenes — pairs with apply_atmosphere. LOCAL/visual-only.
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'FlickerLight'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="style">Default flicker preset baked into the component (editable per-instance in the inspector). Defaults to 'Candle'. One of: Candle | Fluorescent | Faulty | Pulse | Lightning.</param>
/// <param name="intensity">Flicker depth 0..1: 0 = steady, 1 = full blackouts / double-bright flashes. Defaults to 0.5.</param>
/// <param name="speed">Speed multiplier for the whole pattern. Defaults to 1.</param>
/// <param name="lightId">GUID of a GameObject holding a light component to attach to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "add_flicker_light" )]
public static Task<object> AddFlickerLight( string name = null, string directory = null, string style = null, double? intensity = null, double? speed = null, string lightId = null )
=> McpGate.Run( "add_flicker_light", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "style", style ), ( "intensity", intensity ), ( "speed", speed ), ( "lightId", lightId ) ) );
/// <summary>
/// Generate static conveniences over the SDK's BUILT-IN camera effects —
/// CameraComponent.AddShake(amplitude, frequency, duration), AddPunch(Vector3 direction, amplitude,
/// frequency, duration, fovAmplitude), AddPunch(Angles, ...) and AddTilt(Angles, duration,
/// easeTime), all fire-and-forget, self-expiring, whitelist-verified in sandboxed game code
/// 2026-07-13: a sealed Component exposing {name}.Shake/ShakeAt/Punch/PunchAngles/Tilt statics that
/// resolve the main camera (Scene.Camera, else IsMainCamera search, else first camera; warn +
/// return null when the scene has none) and return the live Sandbox.CameraEffectSystem.BaseEffect
/// (Stop()/IsDone; ShakeAt sets Epicenter+Radius for distance falloff), plus one-word preset
/// triggers — HitPunch() / ExplosionShake() / ExplosionShakeAt(position, radius) / LandingTilt() —
/// driven by [Property] tunables. Statics work with NO instance placed; place the component only to
/// tune presets in the inspector. RELATIONSHIP: create_camera_shake is the CONTINUOUS trauma model
/// (AddTrauma accumulates and decays); these built-ins are ONE-SHOT engine effects — they compose
/// safely, but don't fire both for the same event or hits feel doubled. Returns {created, path,
/// className, staticApi[], presetTriggers[], propertyNames[], note}. Next: trigger_hotload +
/// get_compile_errors, call the statics from game code (e.g. {name}.Shake(4, 25, 0.8) on explosion)
/// or attach via targetId and trigger presets. Limits & honesty: compile + camera resolution
/// verified; the editor cannot judge FEEL — tune amplitudes in a human playtest; effects are LOCAL
/// visuals (wrap in [Rpc.Broadcast] for everyone). Refused during play mode; refuses to overwrite
/// an existing file.
/// </summary>
/// <param name="name">Class/file name. Defaults to 'CameraFx'. Sanitized to a valid C# identifier.</param>
/// <param name="directory">Subdirectory under the project root for the .cs file. Defaults to 'Code'.</param>
/// <param name="hitPunchDirection">HitPunch preset: punch direction. Defaults to Vector3.Backward (camera kicks back). As "x,y,z" (or JSON {x,y,z}).</param>
/// <param name="hitPunchAmplitude">HitPunch preset: positional kick strength. Defaults to 8.</param>
/// <param name="hitPunchFrequency">HitPunch preset: oscillation frequency. Defaults to 20.</param>
/// <param name="hitPunchDuration">HitPunch preset: seconds. Defaults to 0.25.</param>
/// <param name="hitPunchFovAmplitude">HitPunch preset: FOV kick amount (0 = none). Defaults to 3.</param>
/// <param name="explosionShakeAmplitude">ExplosionShake preset: shake strength. Defaults to 5.</param>
/// <param name="explosionShakeFrequency">ExplosionShake preset: oscillation frequency. Defaults to 25.</param>
/// <param name="explosionShakeDuration">ExplosionShake preset: seconds. Defaults to 0.8.</param>
/// <param name="landingTiltAngles">LandingTilt preset as {x: pitch, y: yaw, z: roll} degrees (or 'p,y,r'). Defaults to 5 pitch / 0 yaw / 2 roll. As "x,y,z" (or JSON {x,y,z}).</param>
/// <param name="landingTiltDuration">LandingTilt preset: seconds the tilt lasts. Defaults to 0.35.</param>
/// <param name="landingTiltEase">LandingTilt preset: ease-in/out time within the duration. Defaults to 0.15.</param>
/// <param name="targetId">GUID of a GameObject to attach the component to — only needed to tune presets in the inspector; the statics work with no instance (only attaches if the type is already in the TypeLibrary — hotload first).</param>
[McpTool( "create_camera_effects" )]
public static Task<object> CreateCameraEffects( string name = null, string directory = null, string hitPunchDirection = null, double? hitPunchAmplitude = null, double? hitPunchFrequency = null, double? hitPunchDuration = null, double? hitPunchFovAmplitude = null, double? explosionShakeAmplitude = null, double? explosionShakeFrequency = null, double? explosionShakeDuration = null, string landingTiltAngles = null, double? landingTiltDuration = null, double? landingTiltEase = null, string targetId = null )
=> McpGate.Run( "create_camera_effects", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "hitPunchDirection", hitPunchDirection ), ( "hitPunchAmplitude", hitPunchAmplitude ), ( "hitPunchFrequency", hitPunchFrequency ), ( "hitPunchDuration", hitPunchDuration ), ( "hitPunchFovAmplitude", hitPunchFovAmplitude ), ( "explosionShakeAmplitude", explosionShakeAmplitude ), ( "explosionShakeFrequency", explosionShakeFrequency ), ( "explosionShakeDuration", explosionShakeDuration ), ( "landingTiltAngles", landingTiltAngles ), ( "landingTiltDuration", landingTiltDuration ), ( "landingTiltEase", landingTiltEase ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a trauma-based camera shake component (the standard game-feel model: events add Trauma
/// 0..1, shake magnitude = Trauma², smooth Perlin offsets — not white-noise jitter — and Trauma
/// decays every frame, so explosions slam and footsteps barely register). Attach the generated
/// component to the CAMERA GameObject; fire from any game code via the static
/// <Name>.Shake(0.4f). Applies in OnPreRender AFTER controllers position the camera, with an
/// un-apply guard so it neither fights a controller-driven camera nor accumulates on a static one,
/// and restores the camera when trauma hits zero. LOCAL-only (no [Sync]) — call Shake inside an
/// [Rpc.Broadcast] handler if every client should feel it. Optionally attached to an existing
/// GameObject by GUID (after a hotload).
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'CameraShake'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="maxOffset">Positional shake at full trauma, in world units. Defaults to 6.</param>
/// <param name="maxAngle">Rotational shake at full trauma, in degrees (applied to pitch/yaw/roll). Defaults to 4.</param>
/// <param name="frequency">Noise speed — higher = violent rattle, lower = drunken sway. Defaults to 10.</param>
/// <param name="decayPerSecond">How much trauma drains per second. Defaults to 1.5.</param>
/// <param name="targetId">GUID of the camera GameObject to attach to (only attaches if the type is already loaded — hotload first).</param>
[McpTool( "create_camera_shake" )]
public static Task<object> CreateCameraShake( string name = null, string directory = null, double? maxOffset = null, double? maxAngle = null, double? frequency = null, double? decayPerSecond = null, string targetId = null )
=> McpGate.Run( "create_camera_shake", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "maxOffset", maxOffset ), ( "maxAngle", maxAngle ), ( "frequency", frequency ), ( "decayPerSecond", decayPerSecond ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a combo system: a sealed Component (the authoritative, headless state) PLUS a small
/// Razor HUD (PanelComponent + scss). Three files: <Name>.cs + <Name>Hud.razor +
/// <Name>Hud.razor.scss. The component exposes a static Bump() — call it on every hit and the
/// Count rises; an idle window (ComboWindowSeconds, tracked with TimeSince) resets it; the
/// Multiplier steps up through [Property] tier thresholds (Tier2Hits/Tier3Hits/Tier4Hits =>
/// 2x/3x/4x); and a static OnComboChanged(int count, float multiplier) event fires so any HUD/audio
/// reacts without a reference (Bump() targets the active instance, so callers never need a handle —
/// attach ONE to a persistent object). The HUD subscribes to OnComboChanged, shows "<count>
/// HITS x<mult>", and pulses via a CSS animation on every change (razor_lint-safe: BuildHash
/// folds count/mult/pulse, no switch-expressions / non-ASCII in @code, class-selector SCSS root;
/// host it under a ScreenPanel via add_screen_panel). Pairs with create_health_system /
/// create_floating_combat_text — call Bump() from the damage path and Spawn a popup that reflects
/// the multiplier.
/// </summary>
/// <param name="name">Class name for the combo component; the HUD is generated as <Name>Hud. Defaults to 'ComboMeter'.</param>
/// <param name="directory">Subdirectory for the generated .cs + .razor + .razor.scss. Defaults to 'Code'.</param>
/// <param name="comboWindowSeconds">Idle seconds before the combo resets back to zero (clamped to >= 0.25). Defaults to 3.</param>
[McpTool( "create_combo_meter" )]
public static Task<object> CreateComboMeter( string name = null, string directory = null, double? comboWindowSeconds = null )
=> McpGate.Run( "create_combo_meter", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "comboWindowSeconds", comboWindowSeconds ) ) );
/// <summary>
/// Generate a hand-authored cutscene player component that needs NO .movie asset — the zero-asset
/// alternative to the MovieMaker family (add_movie_player/play_movie, which play keyframed .movie
/// clips authored in the editor's Movie Maker dock). You author the shots directly in the inspector
/// as parallel lists: ShotPositions (Vector3), ShotAngles (pitch/yaw/roll — Angles, not raw
/// quaternions), ShotHoldSeconds, ShotBlendSeconds, and an optional per-shot ShotLookAt GameObject
/// (aim at a target instead of using ShotAngles). At runtime it takes over the main camera
/// (Scene.Camera) in OnPreRender ONLY while playing — smoothstep-eased Vector3.Lerp +
/// Rotation.Slerp between shots — captures the camera's prior transform and restores it exactly
/// when finished (same un-apply discipline as create_camera_shake). Play from any game code via the
/// static <Name>.Play() (first director) or <Name>.Play("name") (matches CutsceneName);
/// subscribe the static <Name>.OnCutsceneFinished, or gate game logic on the static
/// <Name>.IsCutscenePlaying. LockInput freezes player input each frame via
/// Input.ClearActions() while still reading the SkipAction press first so the cutscene stays
/// skippable. Optional letterbox generates a razor_lint-safe black-bars overlay panel (host under a
/// ScreenPanel) shown while IsCutscenePlaying. LOCAL-only (each client renders its own view) —
/// trigger inside an [Rpc.Broadcast] for all clients. Attach to ANY GameObject; it drives the
/// camera itself and does not need to sit on the camera. Returns { created, path, className,
/// skipAction, lockInput, letterbox, nextSteps } — path is the generated .cs (letterbox lists the
/// overlay files when enabled). Follow with trigger_hotload, then get_compile_errors, then attach
/// via add_component_with_properties (component=className).
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'CutsceneDirector'.</param>
/// <param name="directory">Subdirectory for the generated files. Defaults to 'Code'.</param>
/// <param name="skipAction">Input action that ends the cutscene early (read before input is cleared, so a locked cutscene is still skippable). Sanitized to a safe token. Empty = not skippable. Defaults to 'jump'.</param>
/// <param name="lockInput">Freeze player input during playback via Input.ClearActions() each frame. Defaults to true.</param>
/// <param name="letterbox">Also generate a razor_lint-safe letterbox overlay (two black bars, shown while IsCutscenePlaying — host it under a ScreenPanel). Defaults to false.</param>
[McpTool( "create_cutscene_director" )]
public static Task<object> CreateCutsceneDirector( string name = null, string directory = null, string skipAction = null, bool? lockInput = null, bool? letterbox = null )
=> McpGate.Run( "create_cutscene_director", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "skipAction", skipAction ), ( "lockInput", lockInput ), ( "letterbox", letterbox ) ) );
/// <summary>
/// Generate an NPC/story dialogue system — a sealed state+data Component paired with a
/// razor_lint-safe Razor HUD panel. Lines are authored in the inspector as a List<string>
/// using the 'Speaker: text' convention (the part before the first colon is the speaker). The
/// generated HUD panel binds to <Name>.Current automatically (no wiring) and renders the
/// current line with a TimeSince-driven typewriter reveal at CharsPerSecond, folding the visible
/// substring into BuildHash so it re-renders as characters appear. Press the AdvanceAction once to
/// snap the whole line into view instantly, again to move to the next line; dismissing the last
/// line ends the conversation. Start from any game code via the static
/// <Name>.StartDialogue(string[] lines) or set Lines and call the instance Begin(). Static
/// events for hooks: OnLineShown(index, speaker) — pair with add_lipsync to drive facial morphs /
/// audio per line — and OnDialogueFinished when the conversation ends. Pairs with
/// create_interactable to trigger dialogue on use. LOCAL-only (per-client HUD) — call StartDialogue
/// inside an [Rpc.Broadcast] if every client should see it. Attach the panel under a ScreenPanel
/// (add_screen_panel) so the HUD renders. Returns { created, path, className, charsPerSecond,
/// advanceAction, panel, nextSteps } — panel lists the generated '<Name>Panel' HUD files.
/// Follow with trigger_hotload, then get_compile_errors, then attach both components via
/// add_component_with_properties.
/// </summary>
/// <param name="name">Class name for the generated dialogue component (the HUD panel is generated as '<Name>Panel'). Defaults to 'DialogueSystem'.</param>
/// <param name="directory">Subdirectory for the generated files. Defaults to 'Code'.</param>
/// <param name="charsPerSecond">Typewriter reveal speed in characters per second (clamped to >= 1). Defaults to 40.</param>
/// <param name="advanceAction">Input action that completes the reveal / advances to the next line. Sanitized to a safe token. Defaults to 'use'.</param>
[McpTool( "create_dialogue_system" )]
public static Task<object> CreateDialogueSystem( string name = null, string directory = null, double? charsPerSecond = null, string advanceAction = null )
=> McpGate.Run( "create_dialogue_system", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "charsPerSecond", charsPerSecond ), ( "advanceAction", advanceAction ) ) );
/// <summary>
/// Generate a floating combat text component — rising, fading, camera-billboarded world-space
/// popups for damage numbers, '+10 gold', pickup names. TextRenderer-based: no Razor, no
/// WorldPanel, zero UI setup. Nothing to place in the scene — the generated class carries a static
/// factory: <Name>.Spawn(position, "-25", Color.Red[, sizeMultiplier]) spawns a popup that
/// rises at RiseSpeed, fades over Lifetime, and destroys itself. Pairs with create_health_system
/// (spawn from the damage path so every hit prints its number). LOCAL-only — spawn inside an
/// [Rpc.Broadcast] handler if every client should see it.
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'FloatingCombatText'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="riseSpeed">World units the popup rises per second. Defaults to 48.</param>
/// <param name="lifetime">Seconds until the popup is fully faded and destroyed. Defaults to 1.1.</param>
/// <param name="fontSize">Base font size baked into Spawn() (the optional Spawn size argument multiplies it). Defaults to 24.</param>
[McpTool( "create_floating_combat_text" )]
public static Task<object> CreateFloatingCombatText( string name = null, string directory = null, double? riseSpeed = null, double? lifetime = null, double? fontSize = null )
=> McpGate.Run( "create_floating_combat_text", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "riseSpeed", riseSpeed ), ( "lifetime", lifetime ), ( "fontSize", fontSize ) ) );
/// <summary>
/// Generate a sealed Component that floats the OWNER'S display name above a networked player.
/// TextRenderer-based (not WorldPanel): a nametag is one short string with a distance fade, so a
/// TextRenderer on a managed child object is far simpler than a WorldPanel + Razor + WorldInput
/// stack — no UI assets, no panel host, per-frame alpha is a one-liner (mirrors
/// create_floating_combat_text). Reads GameObject.Network.Owner.DisplayName (Owner and
/// OwnerConnection are the same Connection on this SDK; falls back to the object name offline).
/// Visibility is the INVERSE of the usual proxy guard: it renders only when
/// GameObject.Network.IsProxy is true — i.e. on OTHER clients' copies of the player — so you never
/// see a tag over your own head (offline / no networking => IsProxy false everywhere => no
/// tags, expected). Spawns a CHILD GameObject for the text so billboarding never rotates the player
/// model; cleaned up on disable. [Property] MaxDistance fades the tag out with distance,
/// HeightOffset floats it above the head, FontSize sizes it. Attach to the ROOT of your networked
/// player object. Pairs with create_networked_player (add it to the generated player prefab so
/// every remote player is labeled). Returns { created, path, className, maxDistance, heightOffset,
/// note, nextSteps }. Follow with trigger_hotload, then get_compile_errors, then attach via
/// add_component_with_properties (component=className).
/// </summary>
/// <param name="name">Class name for the generated component. Defaults to 'ProxyNametag'.</param>
/// <param name="directory">Subdirectory for the generated .cs file. Defaults to 'Code'.</param>
/// <param name="maxDistance">Full alpha up close; fades to zero as the camera approaches this distance and is hidden past it, in world units. Defaults to 2000.</param>
/// <param name="heightOffset">Height above the object's origin to float the tag, in world units (~72 clears a Citizen's head). Defaults to 72.</param>
[McpTool( "create_proxy_nametag" )]
public static Task<object> CreateProxyNametag( string name = null, string directory = null, double? maxDistance = null, double? heightOffset = null )
=> McpGate.Run( "create_proxy_nametag", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "maxDistance", maxDistance ), ( "heightOffset", heightOffset ) ) );
/// <summary>
/// Generate a Razor screen-panel HUD (.razor + .razor.scss) showing the active round/phase time
/// remaining as mm:ss with the phase/state name above it. NO code coupling to your round machine:
/// at runtime it discovers one by TypeLibrary property reflection — same-GameObject components
/// first, then the whole scene, re-scanning every 2s while unbound — matching either shipped
/// machine shape: create_round_phase_machine output (a [Sync] TimeUntil 'PhaseTimer' +
/// 'CurrentPhase' enum for the label) or create_round_state_machine output (a manager with
/// 'StateIndex' + 'Current', whose active state carries a [Sync] TimeUntil 'TimeLeft' +
/// 'Identifier'). First matching component wins; any hand-written machine exposing those member
/// names also binds. Adaptive BuildHash folds the WHOLE second remaining so the panel re-renders at
/// 1 Hz, not every frame. Optional low-time warning: at/below lowTimeSeconds the clock gets the
/// 'low' CSS class (red by default). Shows '--:--' (and fades out via the 'unbound' class) until a
/// machine exists. Returns { created, razorPath, scssPath, className, lowTimeSeconds, note,
/// nextSteps }. Renders NOTHING without a ScreenPanel host: follow with trigger_hotload, then
/// add_screen_panel, then add_component_with_properties (component=className) on the same object;
/// verify with capture_view (renderUI=true) in play mode.
/// </summary>
/// <param name="name">Class name for the generated panel. Defaults to 'RoundTimerHud'.</param>
/// <param name="directory">Subdirectory for the generated .razor + .razor.scss. Defaults to 'Code/UI'.</param>
/// <param name="lowTimeSeconds">Remaining-seconds threshold at/below which the clock gets the 'low' warning class (clamped to >= 0; 0 disables). Editable per-instance via the LowTimeSeconds [Property]. Defaults to 10.</param>
[McpTool( "create_round_timer_hud" )]
public static Task<object> CreateRoundTimerHud( string name = null, string directory = null, double? lowTimeSeconds = null )
=> McpGate.Run( "create_round_timer_hud", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "lowTimeSeconds", lowTimeSeconds ) ) );
/// <summary>
/// Generate a diegetic, clickable world-space UI: a Razor PanelComponent (+ .razor.scss) meant to
/// sit on a GameObject that ALSO carries a Sandbox.WorldPanel — the WorldPanel is the world-space
/// render surface (PanelSize / RenderScale / InteractionRange), this component is the actual UI it
/// renders (PanelComponent has no world-panel mode of its own). Ships two example buttons wired to
/// @onclick that raise a static event OnButtonPressed(string id), so game code reacts WITHOUT
/// editing the panel (subscribe: <Name>.OnButtonPressed += id => ...). razor_lint-safe by
/// construction (BuildHash override, no switch-expressions / non-ASCII in @code, class-selector
/// SCSS root). SCENE PREREQUISITE FOR CLICKS: a WorldPanel's buttons only fire when a
/// Sandbox.WorldInput exists in the scene (typically on the camera or the player) with its
/// LeftMouseAction set to your click input action (e.g. "attack1"); on this SDK WorldInput drives
/// itself from the camera + that action — there is no manual ray to feed, and WorldInput.Hovered
/// (read-only) reflects the panel under the cursor. Without a WorldInput present, @onclick never
/// fires. Setup: add_world_panel to a GameObject, add this component to the same object, add a
/// WorldInput to the scene.
/// </summary>
/// <param name="name">Class name for the generated panel. Defaults to 'WorldPanelUi'.</param>
/// <param name="directory">Subdirectory for the generated .razor + .razor.scss. Defaults to 'Code/UI'.</param>
/// <param name="title">Heading text baked into the panel (editable per-instance via the Title [Property]). Defaults to 'Interact'.</param>
[McpTool( "create_worldpanel_ui" )]
public static Task<object> CreateWorldpanelUi( string name = null, string directory = null, string title = null )
=> McpGate.Run( "create_worldpanel_ui", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "title", title ) ) );
/// <summary>
/// Generate a lipsync dialogue performer — NPCs SPEAK their lines with MOVING MOUTHS: a sealed
/// Component holding a [Property] line list (speaker GameObject name + text) that, per line, (a)
/// mirrors the line into a generated create_dialogue_system HUD when one exists (loose TypeLibrary
/// capability bind: List<string> Lines + Begin() + bool IsActive — neither system references
/// the other), (b) speaks the text via Sandbox.Speech.Synthesizer positionally AT the speaker
/// (per-speaker voice name/gender/age/rate via the Voices list), (c) drives the speaker's
/// SkinnedModelRenderer mouth morphs from the live viseme stream — Handle.LipSync.Visemes
/// (IReadOnlyList<float> in the engine's 15-viseme order, read live from
/// Sandbox.LipSync.VisemeNames 2026-07-13) multiplied through the model's own baked
/// viseme->morph table (Model.GetVisemeMorph — verified nonzero on Citizen, e.g. viseme_AA ->
/// openjawL/R; NOT a hand-guessed morph map), with MorphScale + smoothing, and (d) advances when
/// the audio handle stops (LineGapSeconds pause, LineTimeoutSeconds safety-skip, Skip() to cut a
/// line short, StopDialogue() to abort). Static events: OnLineStarted(dialogue, lineIndex, speaker)
/// + OnDialogueFinished(dialogue). Returns {created, path, className, lineCount, voiceCount,
/// propertyNames[], note}. Next: trigger_hotload + get_compile_errors, attach (targetId re-call or
/// add_component_with_properties), fill Lines/Voices in the inspector or bake them via params, call
/// Begin() from game code (or autoStart:true; pair with create_interactable). Limits & honesty:
/// the editor cannot playtest audio, so the LIVE viseme stream is RUNTIME-UNVERIFIED — the
/// generated LogVisemes() helper + DebugLogVisemes property confirm it in seconds in play mode (the
/// API surface and mapping data ARE live-verified); models without baked viseme data log a warning
/// and stay audio-only (Citizen has it); voices are machine/OS-specific and TrySetVoice is
/// best-effort; LOCAL-only — call Begin() inside an [Rpc.Broadcast] for everyone; a bound HUD's own
/// advance input stays active (it only ends that HUD's display, not the audio). Refused during play
/// mode; refuses to overwrite an existing file.
/// </summary>
/// <param name="name">Class/file name. Defaults to 'LipsyncDialogue'. Sanitized to a valid C# identifier.</param>
/// <param name="directory">Subdirectory under the project root for the .cs file. Defaults to 'Code'.</param>
/// <param name="lines">Dialogue lines baked as inspector-editable defaults. Defaults to a two-line demo on the own GameObject. JSON array.</param>
/// <param name="voices">Per-speaker voice settings baked as defaults. Defaults to empty (every speaker uses the OS default voice). JSON array.</param>
/// <param name="volume">Playback volume for spoken lines. Defaults to 1.</param>
/// <param name="positional">true (default): 3D audio parented to the speaker GameObject. false: flat 2D narrator voice.</param>
/// <param name="driveMouth">true (default): drive the speaker's SkinnedModelRenderer mouth morphs from the viseme stream. false: audio-only.</param>
/// <param name="morphScale">Multiplier on viseme-derived morph weights (same idea as Sandbox.LipSync.MorphScale). Defaults to 1.</param>
/// <param name="mouthSmoothSeconds">Seconds of exponential smoothing on mouth morphs (0 = raw viseme weights). Defaults to 0.05.</param>
/// <param name="lineGapSeconds">Pause between a line's audio ending and the next line starting. Defaults to 0.2.</param>
/// <param name="lineTimeoutSeconds">Safety: a line whose audio never starts (synthesis pending/failed) is skipped after this many seconds. Defaults to 20.</param>
/// <param name="bindHud">true (default): loosely bind a create_dialogue_system HUD in the scene (TypeLibrary capability match) and mirror each line into it. false: no HUD mirroring.</param>
/// <param name="autoStart">true: Begin() fires in OnStart. Defaults to false (call Begin() from game code).</param>
/// <param name="debugLogVisemes">true: log the live viseme stream ~4x/second while speaking — the fast way to runtime-verify the mouth drive. Defaults to false.</param>
/// <param name="targetId">GUID of a GameObject to attach the component to (only attaches if the type is already in the TypeLibrary — hotload first, then re-call or use add_component_with_properties).</param>
[McpTool( "generate_lipsync_dialogue" )]
public static Task<object> GenerateLipsyncDialogue( string name = null, string directory = null, JsonNode lines = null, JsonNode voices = null, double? volume = null, bool? positional = null, bool? driveMouth = null, double? morphScale = null, double? mouthSmoothSeconds = null, double? lineGapSeconds = null, double? lineTimeoutSeconds = null, bool? bindHud = null, bool? autoStart = null, bool? debugLogVisemes = null, string targetId = null )
=> McpGate.Run( "generate_lipsync_dialogue", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "lines", lines ), ( "voices", voices ), ( "volume", volume ), ( "positional", positional ), ( "driveMouth", driveMouth ), ( "morphScale", morphScale ), ( "mouthSmoothSeconds", mouthSmoothSeconds ), ( "lineGapSeconds", lineGapSeconds ), ( "lineTimeoutSeconds", lineTimeoutSeconds ), ( "bindHud", bindHud ), ( "autoStart", autoStart ), ( "debugLogVisemes", debugLogVisemes ), ( "targetId", targetId ) ) );
}