Editor/Mcp/BridgeNpcTools.cs
// 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>
/// NPC brains (state machines), spawners, patrol routes, and perception simulation.
/// </summary>
[McpToolset( "bridge_npc", "NPC brains (state machines), spawners, patrol routes, and perception simulation." )]
public static class BridgeNpcTools
{
/// <summary>
/// Wire a placed route (or an arbitrary ordered GUID list) into an NpcBrain's Waypoints list on a
/// target NPC. This is the list-of-GameObject-references case that plain set_property can't
/// express. Pass either waypointIds (explicit order) or routeId (a route parent whose children
/// become the waypoints in hierarchy order). The list count is returned; List<GameObject>
/// refs may read back as handles/GUIDs via get_property, so trust the count or confirm patrol in
/// play mode.
/// </summary>
/// <param name="npcId">GUID of the GameObject holding the NpcBrain (or any component with a List<GameObject> waypoint property).</param>
/// <param name="waypointIds">Ordered waypoint GameObject GUIDs (e.g. from place_patrol_route). Takes precedence over routeId.</param>
/// <param name="routeId">A route parent GUID whose children (in hierarchy order) become the waypoints.</param>
/// <param name="property">The List<GameObject> property name to set. Defaults to 'Waypoints'. (Use 'SpawnPoints' to wire spawn points on a spawner.).</param>
[McpTool( "assign_patrol_route" )]
public static Task<object> AssignPatrolRoute( string npcId, string[] waypointIds = null, string routeId = null, string property = null )
=> McpGate.Run( "assign_patrol_route", McpGate.Args( ( "npcId", npcId ), ( "waypointIds", waypointIds ), ( "routeId", routeId ), ( "property", property ) ) );
/// <summary>
/// Generate an NpcBrain Component: a behavior state machine
/// (Idle/Patrol/Wander/Chase/Search/Flee/Ambush) driven by occlusion-aware perception — FOV cone +
/// sight range + a line-of-sight trace (respects walls/trees) + proximity hearing — with
/// last-known-position memory (lose-LOS -> search -> give up -> resume). This is the
/// decision layer on top of bake_navmesh / NavMeshAgent movement. Pick a behavior preset, then tune
/// via the generated [Property] fields with set_property. After generating: trigger_hotload +
/// get_compile_errors, place a route with place_patrol_route + assign_patrol_route, bake_navmesh,
/// and verify perception in EDIT mode with simulate_npc_perception (chase/search behavior needs
/// play mode). The component is added to a GameObject like any other; it auto-adds a NavMeshAgent
/// in OnStart.
/// </summary>
/// <param name="name">Class/file name. Defaults to 'NpcBrain'. 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="behavior">Preset (sets StartState + flee toggle): 'patrol' (walk waypoints), 'guard' (Ambush near spawn until a target enters range), 'hunter' (patrol->chase->search, the Sasquatch), 'swarm' (wander/idle->chase nearest, RUN mobs), 'skittish' (chase but flee on low health). The generated file is the same shape; the preset just changes defaults. Defaults to 'hunter'. One of: patrol | guard | hunter | swarm | skittish.</param>
/// <param name="targetTag">Tag the NPC hunts (its candidates are GameObjects with this tag). Defaults to 'player'.</param>
/// <param name="moveSpeed">Patrol/wander speed (NavMeshAgent MaxSpeed). Default 130.</param>
/// <param name="chaseSpeed">Chase/flee speed. Default 200.</param>
/// <param name="sightRange">Max sight distance. Default 1500.</param>
/// <param name="fovDegrees">Full field-of-view cone angle in degrees. Default 110. (Baked into a cosine threshold for cheap, trig-free checks.).</param>
/// <param name="eyeHeight">Trace origin height above the NPC's feet. Default 64.</param>
/// <param name="hearingRadius">Proximity-hearing radius — a target within it is investigated (sets last-known-pos) but NOT instantly aggroed. Default 600.</param>
/// <param name="giveUpTime">Seconds to search after losing line-of-sight before giving up and resuming the start state. Default 6.</param>
/// <param name="searchRadius">Wander radius around the last-known position while searching. Default 400.</param>
/// <param name="waypointStopDistance">How close the NPC must get to a waypoint/target before it counts as reached. Default 80.</param>
/// <param name="canFlee">Enable the Flee state (else the NPC never flees). Defaults from the preset.</param>
/// <param name="fleeHealthFrac">Flee when CurrentHealthFrac drops to/below this (the game sets CurrentHealthFrac 0..1). Default 0.25.</param>
/// <param name="networked">When true (default), emit a host-authoritative brain: 'if (IsProxy) return;' + [Sync] CurrentState. NOTE: a no-session solo playtest makes everything a proxy, so a networked brain won't think until a host session exists — pass false to iterate solo in the edit scene.</param>
[McpTool( "create_npc_brain" )]
public static Task<object> CreateNpcBrain( string name = null, string directory = null, string behavior = null, string targetTag = null, double? moveSpeed = null, double? chaseSpeed = null, double? sightRange = null, double? fovDegrees = null, double? eyeHeight = null, double? hearingRadius = null, double? giveUpTime = null, double? searchRadius = null, double? waypointStopDistance = null, bool? canFlee = null, double? fleeHealthFrac = null, bool? networked = null )
=> McpGate.Run( "create_npc_brain", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "behavior", behavior ), ( "targetTag", targetTag ), ( "moveSpeed", moveSpeed ), ( "chaseSpeed", chaseSpeed ), ( "sightRange", sightRange ), ( "fovDegrees", fovDegrees ), ( "eyeHeight", eyeHeight ), ( "hearingRadius", hearingRadius ), ( "giveUpTime", giveUpTime ), ( "searchRadius", searchRadius ), ( "waypointStopDistance", waypointStopDistance ), ( "canFlee", canFlee ), ( "fleeHealthFrac", fleeHealthFrac ), ( "networked", networked ) ) );
/// <summary>
/// Generate a daily-routine NPC brain: a [Property] list of schedule entries (startHour/endHour
/// 0..24, taskName, target = named scene GameObject or fixed position), the hour read from any
/// create_day_night_clock component (capability match: a float TimeOfDay property, same GameObject
/// first then scene-wide) with an HONEST fallback to its own internal clock when none exists (check
/// the generated UsingClockComponent bool), walking the NPC to the active entry's target and idling
/// outside the schedule, plus a static OnTaskChanged(brain, taskName) event and [Sync(FromHost)]
/// CurrentTask. Entries with endHour < startHour wrap past midnight. Returns {created, path,
/// className, tasks[], propertyNames[], note}. Next: trigger_hotload + get_compile_errors, attach
/// (targetId or add_component_with_properties), create the named target GameObjects (e.g.
/// 'WorkSpot'), pair with create_day_night_clock for shared time, verify via get_runtime_property
/// CurrentTask in play mode. Limits: default movement is a direct transform walk (walks through
/// walls) — pass useNavMeshAgent:true for pathfinding (then bake_navmesh is REQUIRED); a clock with
/// a different shape (e.g. 0..1 DayProgress) will NOT bind; networked default true won't tick in a
/// no-session solo playtest (networked:false to iterate). Refused during play mode; refuses to
/// overwrite an existing file.
/// </summary>
/// <param name="name">Class/file name. Defaults to 'NpcScheduleBrain'. 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="schedule">Schedule entries baked as inspector-editable defaults. Defaults to Work 8-17 @ 'WorkSpot', Relax 17-22 @ 'HomeSpot' (idles/sleeps otherwise). JSON array.</param>
/// <param name="moveSpeed">Walk speed in world units/s. Defaults to 100.</param>
/// <param name="arriveDistance">Distance at which the NPC counts as arrived and idles at the spot. Defaults to 32.</param>
/// <param name="useNavMeshAgent">true: move via NavMeshAgent.MoveTo (real pathfinding — REQUIRES bake_navmesh or the NPC won't move). Defaults to false (direct transform walk, no navmesh needed, walks through walls).</param>
/// <param name="fallbackDayLengthSeconds">Internal fallback clock only: real seconds per 24 in-game hours when NO TimeOfDay clock component exists. Defaults to 600.</param>
/// <param name="fallbackStartHour">Internal fallback clock only: starting hour 0..24. Defaults to 8.</param>
/// <param name="networked">true (default): host-authoritative (IsProxy guard) + [Sync(FromHost)] CurrentTask. false: local build for solo iteration.</param>
/// <param name="targetId">GUID of the NPC GameObject to attach to (only attaches if the type is already in the TypeLibrary — hotload first).</param>
[McpTool( "create_npc_schedule_brain" )]
public static Task<object> CreateNpcScheduleBrain( string name = null, string directory = null, JsonNode schedule = null, double? moveSpeed = null, double? arriveDistance = null, bool? useNavMeshAgent = null, double? fallbackDayLengthSeconds = null, double? fallbackStartHour = null, bool? networked = null, string targetId = null )
=> McpGate.Run( "create_npc_schedule_brain", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "schedule", schedule ), ( "moveSpeed", moveSpeed ), ( "arriveDistance", arriveDistance ), ( "useNavMeshAgent", useNavMeshAgent ), ( "fallbackDayLengthSeconds", fallbackDayLengthSeconds ), ( "fallbackStartHour", fallbackStartHour ), ( "networked", networked ), ( "targetId", targetId ) ) );
/// <summary>
/// Generate a spawner Component that instantiates an NPC prefab over time / in escalating waves at
/// spawn points, capped by maxAlive. RUN's swarm backbone and Sasquatched's round-start spawn.
/// After generating: set NpcPrefab via set_prefab_ref, set SpawnPoints (reuse place_patrol_route to
/// make a set of empties, then assign_patrol_route with property='SpawnPoints'), trigger_hotload +
/// get_compile_errors. Verify by watching the GameObject count over time in play mode. Networked
/// spawns use NetworkSpawn() and are host-only.
/// </summary>
/// <param name="name">Class/file name. Defaults to 'NpcSpawner'.</param>
/// <param name="directory">Subdirectory under the project root. Defaults to 'Code'.</param>
/// <param name="mode">'continuous' (one every interval), 'waves' (a batch every interval, waveCount times), 'burst' (one batch then stop). Default 'waves'. One of: continuous | waves | burst.</param>
/// <param name="count">NPCs per wave (waves) or per batch (burst/continuous batch). Default 5.</param>
/// <param name="interval">Seconds between spawns (continuous) or between waves (waves). Default 8.</param>
/// <param name="waveCount">Number of waves (waves mode). Default 3.</param>
/// <param name="waveGrowth">Multiply count each wave (>1 = escalating). Default 1.0.</param>
/// <param name="radius">Random scatter radius around a spawn point. Default 200.</param>
/// <param name="maxAlive">Cap on concurrent live NPCs (important so swarms don't melt the frame rate). Default 12.</param>
/// <param name="networked">When true (default), spawn via NetworkSpawn() (host-only, try/catch solo-safe) so clients see the NPCs; false = a plain local Clone for solo/edit testing.</param>
[McpTool( "create_npc_spawner" )]
public static Task<object> CreateNpcSpawner( string name = null, string directory = null, string mode = null, double? count = null, double? interval = null, double? waveCount = null, double? waveGrowth = null, double? radius = null, double? maxAlive = null, bool? networked = null )
=> McpGate.Run( "create_npc_spawner", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "mode", mode ), ( "count", count ), ( "interval", interval ), ( "waveCount", waveCount ), ( "waveGrowth", waveGrowth ), ( "radius", radius ), ( "maxAlive", maxAlive ), ( "networked", networked ) ) );
/// <summary>
/// Generate a utility-AI (scored-action) brain: one file with an abstract {name}Action : Component
/// base (Score() 0..1 + Begin/Tick/End lifecycle), a sealed {name}Brain that every EvaluateInterval
/// picks the highest-scoring sibling action (score × ScoreWeight, current action gets
/// +HysteresisBonus so near-ties don't flip-flop), and two example actions — {name}IdleAction
/// (constant fallback score) and {name}WanderAction (desire builds while idle, walks to random
/// points by direct transform movement, no navmesh). How it differs from create_npc_brain: the FSM
/// has a FIXED transition table; here behavior EMERGES from per-frame scores — add behaviors by
/// subclassing the base on the same GameObject, no transition wiring. Returns {created, path,
/// classNames[4], propertyNames[], note}. Next: trigger_hotload + get_compile_errors, attach the
/// brain AND example actions to one GameObject (targetId attaches only the brain), verify in play
/// mode via get_runtime_property CurrentActionName. Limits: networked default true =
/// host-authoritative (won't tick in a no-session solo playtest — use networked:false); actions
/// Tick on the simulating machine only. Refused during play mode; refuses to overwrite an existing
/// file.
/// </summary>
/// <param name="name">System prefix — generates {name}Action / {name}Brain / {name}IdleAction / {name}WanderAction in {name}Ai.cs. Defaults to 'Utility'. 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="evaluateInterval">Seconds between score evaluations (the active action still Ticks every frame). Defaults to 0.25.</param>
/// <param name="hysteresisBonus">Score bonus the current action gets during evaluation — stickiness that prevents flip-flopping between near-tied actions. Defaults to 0.15.</param>
/// <param name="moveSpeed">Example WanderAction walk speed in world units/s. Defaults to 80.</param>
/// <param name="wanderRadius">Example WanderAction roam radius around its start position. Defaults to 300.</param>
/// <param name="networked">true (default): host-authoritative brain (IsProxy guard) + [Sync(FromHost)] CurrentActionName. false: local build for solo iteration.</param>
/// <param name="targetId">GUID of a GameObject to attach the BRAIN to (actions must be added separately; only attaches if the type is already in the TypeLibrary — hotload first).</param>
[McpTool( "create_utility_ai" )]
public static Task<object> CreateUtilityAi( string name = null, string directory = null, double? evaluateInterval = null, double? hysteresisBonus = null, double? moveSpeed = null, double? wanderRadius = null, bool? networked = null, string targetId = null )
=> McpGate.Run( "create_utility_ai", McpGate.Args( ( "name", name ), ( "directory", directory ), ( "evaluateInterval", evaluateInterval ), ( "hysteresisBonus", hysteresisBonus ), ( "moveSpeed", moveSpeed ), ( "wanderRadius", wanderRadius ), ( "networked", networked ), ( "targetId", targetId ) ) );
/// <summary>
/// Place a set of waypoint GameObjects (tagged empties) for a patrol route and group them under a
/// parent route object — authorable in one call. Optionally snaps each point to the ground (raycast
/// down) so waypoints sit on the navmesh, not floating. Returns the route parent GUID + ordered
/// waypoint GUIDs to feed into assign_patrol_route. Validate connectivity afterward with
/// get_navmesh_path between consecutive waypoints (catches a 'point in a wall').
/// </summary>
/// <param name="points">Ordered world positions for the route (at least 2). JSON array.</param>
/// <param name="name">Route name. Defaults to 'PatrolRoute'. Waypoints are named <route>_WP0, _WP1, ...</param>
/// <param name="tag">Tag applied to each waypoint. Defaults to 'waypoint'.</param>
/// <param name="snapToGround">Drop each point onto the surface below via a downward raycast. Default true.</param>
/// <param name="parentId">Existing parent GameObject GUID to nest the waypoints under; otherwise a new route empty is created at the points' centroid.</param>
[McpTool( "place_patrol_route" )]
public static Task<object> PlacePatrolRoute( JsonNode points, string name = null, string tag = null, bool? snapToGround = null, string parentId = null )
=> McpGate.Run( "place_patrol_route", McpGate.Args( ( "points", points ), ( "name", name ), ( "tag", tag ), ( "snapToGround", snapToGround ), ( "parentId", parentId ) ) );
/// <summary>
/// READ-ONLY edit-mode verifier: evaluate the NPC's perception math RIGHT NOW without entering play
/// mode. Given an NPC (reads its NpcBrain SightRange/FovDegrees/EyeHeight/TargetTag + transform)
/// and either a targetId or a point, it runs the SAME line-of-sight check the brain uses — FOV cone
/// (dot vs the baked cosine), sight-range gate, and an occlusion trace from the eye to the target —
/// and reports the result AND why. This is the keystone verifier: it makes the perception layer
/// checkable in edit mode (no flaky screenshot timing) — e.g. place the Sasquatch, place a camper
/// behind a tree, and confirm the tree blocks LOS. Call params override the brain's values, so it
/// also works before/without an NpcBrain (uses defaults). Safe in play mode too (read-only, like
/// raycast).
/// </summary>
/// <param name="npcId">GUID of the NPC GameObject (ideally with an NpcBrain; its perception [Property] values are read).</param>
/// <param name="targetId">GUID of the target GameObject to test visibility to (e.g. a player). Provide this OR point.</param>
/// <param name="point">A raw world point to test visibility to. Provide this OR targetId. As "x,y,z" (or JSON {x,y,z}).</param>
/// <param name="sightRange">Override the sight range for this check (else read from the NpcBrain / default 1500).</param>
/// <param name="fovDegrees">Override the FOV cone angle for this check (else read from the NpcBrain / default 110).</param>
/// <param name="eyeHeight">Override the eye height for this check (else read from the NpcBrain / default 64).</param>
/// <param name="targetTag">Override the target tag (canSee also requires the target to carry this tag; else read from the NpcBrain / default 'player').</param>
[McpTool( "simulate_npc_perception" )]
public static Task<object> SimulateNpcPerception( string npcId, string targetId = null, string point = null, double? sightRange = null, double? fovDegrees = null, double? eyeHeight = null, string targetTag = null )
=> McpGate.Run( "simulate_npc_perception", McpGate.Args( ( "npcId", npcId ), ( "targetId", targetId ), ( "point", point ), ( "sightRange", sightRange ), ( "fovDegrees", fovDegrees ), ( "eyeHeight", eyeHeight ), ( "targetTag", targetTag ) ) );
}