Editor/Mcp/BridgeScaffoldPolishTools.cs

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.

File Access
// 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 &amp; 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
	/// &lt;Name&gt;.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: &lt;Name&gt;.cs + &lt;Name&gt;Hud.razor +
	/// &lt;Name&gt;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 =&gt;
	/// 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 "&lt;count&gt;
	/// HITS x&lt;mult&gt;", 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 &lt;Name&gt;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 &gt;= 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 &lt;Name&gt;.Play() (first director) or &lt;Name&gt;.Play("name") (matches CutsceneName);
	/// subscribe the static &lt;Name&gt;.OnCutsceneFinished, or gate game logic on the static
	/// &lt;Name&gt;.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&lt;string&gt;
	/// using the 'Speaker: text' convention (the part before the first colon is the speaker). The
	/// generated HUD panel binds to &lt;Name&gt;.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
	/// &lt;Name&gt;.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 '&lt;Name&gt;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 '&lt;Name&gt;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 &gt;= 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: &lt;Name&gt;.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 =&gt; IsProxy false everywhere =&gt; 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 &gt;= 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: &lt;Name&gt;.OnButtonPressed += id =&gt; ...). 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&lt;string&gt; 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&lt;float&gt; in the engine's 15-viseme order, read live from
	/// Sandbox.LipSync.VisemeNames 2026-07-13) multiplied through the model's own baked
	/// viseme-&gt;morph table (Model.GetVisemeMorph — verified nonzero on Citizen, e.g. viseme_AA -&gt;
	/// 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 &amp; 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 ) ) );
}