Skip to main content

Types

The contracts VluxyAI is built from. You extend the package by passing in your own module that satisfies one of these types; the package never learns your module exists.

The important ones:

  • [Types.Brain]: a table of states. The part you write for every AI.
  • [Types.Pathfinder]: anything with FindPath. Plans, never moves.
  • [Types.Mover]: anything with Follow and Stop. Moves, never plans.
  • [Types.Sense]: anything with Tick that writes to the Types.Blackboard.
  • [Types.AgentDefinition]: the composition of the above, which Build() validates and turns into an Agent.

Types​

Connection​

interface Connection {
Connected: boolean--

false once disconnected.

Disconnect: (self: Connection) → ()--

Disconnects the handler.

}

What Types.Signal Connect returns. Any cleanup helper that calls Disconnect accepts it.

Signal<T...>​

interface Signal<T...> {
Connect: (
self: Signal<T...>,
handler: (T...) → ()
) → Connection--

Runs handler on every fire.

Once: (
self: Signal<T...>,
handler: (T...) → ()
) → Connection--

Runs handler on the next fire only.

Wait: (self: Signal<T...>) → T...--

Yields until the next fire.

Fire: (
self: Signal<T...>,
T...
) → ()--

Fires every connected handler.

DisconnectAll: (self: Signal<T...>) → ()--

Drops every handler.

Destroy: (self: Signal<T...>) → ()--

Drops every handler and refuses new ones.

}

A small signal with the same surface as RBXScriptSignal, implemented in plain Luau so the core runs outside Roblox. Movers and agents expose these; a Cleaner releases their connections.

NoiseSignal​

type NoiseSignal = Signal<(
number?,
string?,
)>

What a Hearing sense listens to: fired with a world position, an optional loudness (1 by default), an optional kind ("Footstep", "Door") and an optional source, the Instance that made the noise (a player's character, a rig), so a brain can tell whose noise it was.

StepAction​

type StepAction = "Walk" | "Jump" | "Custom"

What a Types.Mover does at a step. Mirrors Enum.PathWaypointAction. Custom steps come from PathfindingModifier labels or your own pathfinder; the mover walks to them like any other step and the Types.Navigator's StepReached tells you when it got there, so a door or a ladder is your code.

Step​

interface Step {
Position: Vector3--

Where to go, as a floor position. Movers add their own height.

Action: StepAction--

What to do on the way there.

Label: string?--

The PathfindingModifier label, or whatever your pathfinder attaches. A Types.StepHandler registered for it runs when the step is reached.

Instance: Instance?--

What the step is about, when the pathfinder knows: the door on a Graph link.

}

One point along a Types.Path.

StepHandler​

type StepHandler = (
agent: Agent,
step: Step,
index: number
) → (
boolean?,
number?
)

What happens at a labelled step: open the door, climb the ladder, play the vault. Registered with Builder:OnStep under the step's Label. Runs in its own thread and may yield; the navigator holds the mover at the step, reports Interacting, and walks the rest of the path when the handler returns. A new target or Stop while it runs abandons the path; the handler should check Agent:IsDestroyed after a long yield.

Return false to say the step could not be taken (a locked door). The path is given up as Failed, and the pathfinder is told to Close that step for the seconds given as the second return (default 10), so the next plan goes another way. Returning nothing or true means the step was taken.

StepRegistration​

interface StepRegistration {
Handler: StepHandler
Tag: string?--

When the step arrives without an Instance (a navmesh link or modifier), the nearest instance carrying this CollectionService tag within Radius of the step is put in step.Instance before the handler runs.

Radius: number?--

Studs. Default 8.

Ready: ((
agent: Agent,
step: Step
) → boolean)?--

Asked first, after Tag has found the instance. true means the step needs nothing (the door is already open), so the agent walks on without stopping and the handler does not run.

}

What Builder:OnStep stores for a label: the handler, how to find the door for a step that does not already know it, and how to tell that there is nothing to do. A plain Types.StepHandler is accepted wherever this is.

DoorDriver​

interface DoorDriver {
IsOpen: (
self: any,
door: Instance
) → boolean--

Open, or opening: an agent reaching it walks on without stopping.

Open: (
self: any,
door: Instance,
agent: Agent
) → ()--

Start opening it. Must not yield; the agent waits OpenTime after it.

IsLocked: ((
self: any,
door: Instance,
agent: Agent
) → boolean)?--

Optional. A locked door is refused and planned around.

OpenTime: ((
self: any,
door: Instance
) → number)?--

Optional. Seconds the door takes to open, which the agent waits after Open before walking through. Default: Doors.Step's OpenTime.

}

How Doors opens a door in your game. The package never moves a door itself: your door script does, and the driver is the few lines that connect the two. Doors.Attributes is a ready-made driver that talks through attributes, for a door script that listens to them.

Path​

type Path = {Step}

An ordered list of steps from where the agent was to where it wants to be. The first step is the agent's own position when the pathfinder includes it; movers cope either way.

Pathfinder​

interface Pathfinder {
FindPath: (
self: any,
from: Vector3,
to: Vector3,
agent: Agent
) → (
string?
)--

Returns a path, or nil and a reason. May yield.

Destroy: ((self: any) → ())?--

Optional cleanup. Called when the agent is destroyed.

Name: string?--

Used in messages, such as a Ladder's joined reasons.

Blocked: Signal<()>?--

Optional. Fired when the world changes under the last path returned; the Navigator re-plans on its cadence.

Close: ((
self: any,
step: Step,
seconds: number
) → ())?--

Optional. A labelled step could not be taken (a locked door): plan around it for seconds. Graph closes the link, Navmesh refuses paths through it; Ladder and Hybrid pass it on.

}

Plans and nothing else. A pathfinder never moves the agent, never creates parts and never keeps a target. from and to may be at any height: from is the mover's root and to is often a player's root or a floor node; the path it returns holds floor positions. An empty path counts as a failure. FindPath runs in its own thread and may yield or error; an error is treated as a failed plan and warned about once. VluxyAI.Pathfinders ships Navmesh, Straight and Ladder; your own is any table with FindPath.

Blocked is how a pathfinder that watches the world (Navmesh listens to its Path.Blocked) tells the navigator that the path being walked is no longer good, so the agent re-plans as soon as the cadence allows instead of walking into the obstacle and waiting for the mover's stuck check. A pathfinder that cannot know leaves it out; Ladder and Hybrid forward it from the rung that produced the current path.

Mover​

interface Mover {
Follow: (
self: any,
path: Path
) → ()--

Starts moving along path, replacing any current path.

Stop: (self: any) → ()--

Halts. Fires Arrived(false) if a path was in progress.

GetPosition: (self: any) → Vector3--

Where the agent's root or pivot is right now: the body, not the floor.

GetSpeed: (self: any) → number--

Studs per second.

SetSpeed: (
self: any,
speed: number
) → ()--

Studs per second.

IsMoving: (self: any) → boolean--

true while a path is in progress.

StepReached: Signal<(
number,
)>--

Fired with the index and step each time one is reached.

Arrived: Signal<boolean>--

Fired once per Follow: true at the end of the path, false if stopped, replaced or stuck.

Destroy: ((self: any) → ())?--

Optional cleanup. Called when the agent is destroyed.

Face: ((
self: any,
position: Vector3?
) → ())?--

Optional. Turn toward position while standing still; nil stops. Agent:LookAt calls it.

Teleport: ((
self: any,
position: Vector3
) → ())?--

Optional. Put the body at position at once, dropping any path. Agent:Teleport calls it.

SetNudge: ((
self: any,
offset: Vector3?
) → ())?--

Optional. Aim at each step plus offset (never the last step), so a steering sense can push the body sideways; nil clears. Neighbours calls it.

GetPivot: ((self: any) → CFrame)?--

Optional. Position and facing. Required for an agent with no Entity: Agent:GetPivot reads it.

Step: ((
self: any,
deltaTime: number
) → ())?--

Optional. A mover with no clock of its own: Agent:Step calls it first, with the same delta time. Leave it out on a mover that runs on Heartbeat, or it moves twice.

}

Executes a Types.Path and nothing else. Rules that keep a mover and the Navigator honest:

  • Follow fires Arrived(false) for the path it replaces before starting the new one, and the new path's outcome is reported on a later frame, never from inside Follow. An empty path is Arrived(false) on the next frame.
  • Follow is called from the navigator's planning thread, so it must not yield.
  • Arrived(true) means the last step was reached. The navigator then checks the target for itself: within ArriveDistance it reports arrival, otherwise it re-plans or reports failure.
  • Arrived(false) from a stuck detector makes the navigator re-plan on its cadence.

VluxyAI.Movers ships Humanoid (uses MoveTo), CFrame (steps a pivot, no rig needed) and Record (a pivot in a table, no Instance at all); your own is any table with this shape. Make the signals with VluxyAI.Signal.new().

Blackboard​

type Blackboard = {[string]: any}

The only thing states read about the world. Senses write to it every tick; states and utility considerations read it and never query the world themselves. Each sense documents the keys it writes. Declare your own blackboard type per AI and cast once at the boundary.

Sense​

interface Sense {
Tick: (
self: any,
agent: Agent,
deltaTime: number
) → ()--

Called once per agent tick. Writes to agent.Blackboard.

Destroy: ((
self: any,
agent: Agent?
) → ())?--

Optional cleanup, called with each agent that is destroyed. A shared sense releases that agent's state and tears down only when none remain; called with no agent it tears down outright.

Configure: ((
self: any,
patch: {[string]: any}
) → ())?--

Optional. Changes options after construction; every built-in has it, so a director can widen a cone or shorten a memory mid-round.

}

Perceives and nothing else. Senses tick in the order they were added, before the navigator and the brain. A sense keeps no per-agent state of its own: memory (SeenAt, HeardAt) lives on the blackboard, so one instance may serve many agents and a state may clear a memory early by setting the key to nil. Distances are math.huge when there is nothing to measure. VluxyAI.Senses ships Sight, Proximity, Hearing, Sounds, Awareness, Watched, Surroundings and Neighbours; your own is any table with Tick.

Transition​

interface Transition {
To: string--

The state to enter. Must exist in the brain; Build() checks.

When: (agent: Agent) → boolean--

Checked every tick, in order, before Update.

}

A declarative transition. The first one whose When returns true wins. To may name the current state, which re-enters it (Exit then Enter); an Update returning the current name does nothing. If When forces a change through Agent:SetState, that change wins and its own result is ignored.

Interrupt​

interface Interrupt {
To: string--

The state to enter. Must exist in the brain; Build() checks.

When: (agent: Agent) → boolean--

Checked every tick, in order, before the current state's own transitions.

Except: {string}?--

States in which this interrupt is not checked, such as the one it leads to or an attack that must finish.

}

A Types.Transition that applies from every state: "hunt whoever I see", "freeze when lit", "stop when the director says so". Written once on the brain instead of in every state. Interrupts are checked in order before the current state's transitions; the first true one wins the tick.

State​

interface State {
Enter: ((agent: Agent) → ())?--

Runs once when the state becomes current.

Update: ((
agent: Agent,
deltaTime: number
) → string?)?--

Runs every tick. Return a state name to change state, or nothing to stay.

Exit: ((agent: Agent) → ())?--

Runs once when the state stops being current.

Transitions: {Transition}?--

Checked in order before Update each tick.

}

One state of a Types.Brain. Every field is optional, so a state can be as small as a single Update. Returning the current state's name from Update does nothing; use Agent:SetState to re-enter a state on purpose. If Update itself forces a change through SetState, its return value is ignored so two transitions never stack.

Brain​

interface Brain {
Initial: string--

The state entered on Start().

States: {[string]: State}--

Every state, keyed by name.

Interrupts: {Interrupt}?--

Transitions that apply from every state, checked before the current state's own.

}

A flat finite state machine as a table. The whole AI, readable top to bottom.

Curve​

type Curve = (x: number) → number

Maps a normalised input in [0, 1] to a score in [0, 1]. Utility.Curves ships the common shapes; any function works.

Consideration<T>​

interface Consideration<T> {
Input: (
agent: Agent,
candidate: T
) → number--

Reads a raw value about the candidate, usually off the blackboard.

Min: number?--

The raw value that maps to 0. Default 0.

Max: number?--

The raw value that maps to 1. Default 1.

Curve: Curve?--

The response curve. Default linear.

Invert: boolean?--

Flip the normalised input before the curve, so a big distance scores low.

}

One factor in scoring a candidate. A candidate's score is the product of its considerations, so any one of them can veto by returning 0.

Scored<T>​

interface Scored<T> {
Candidate: T--

The thing that was scored.

Score: number--

Its score in [0, 1].

}

One row of a Utility.ScoreAll result.

PathStatus​

type PathStatus = "Idle" | "Planning" | "Moving" | "Interacting" | "Arrived" | "Failed"

Where a Navigator is with its current target. Idle means no target or stopped; Interacting means a Types.StepHandler is running at a step and the walk resumes after.

interface NavigationOptions {
RepathInterval: number?--

Seconds between re-plans while the target keeps moving. Must be above 0; the three distances below may be 0. Default 0.5.

RepathDistance: number?--

Studs the target must move before a re-plan happens. Default 2.

MaxPathAge: number?--

Seconds a path is followed before it is planned again even though the target has not moved, so the agent keeps up with a changing level. Must be above 0. Default math.huge (never).

ArriveDistance: number?--

Horizontal studs from the target that count as arrived. Must exceed the two bodies' radii for a chase to ever arrive. Default 2.5.

ArriveHeight: number?--

Height difference allowed for arrival, so a target on the floor above does not count. Default 8.

DirectInterval: number?--

With a direct pathfinder (Builder:UseDirect): seconds between checks that the way to the target is still clear. Must be above 0. Default 0.15.

DirectStuckTime: number?--

Seconds walking direct without moving a stud before the navigator gives up on it and plans. Must be above 0. Default 1.

DirectCooldown: number?--

Seconds after giving up on a direct walk before trying one again. Default 3.

Lead: number?--

Seconds of a moving target's velocity to plan ahead of it, so a chase heads where the player is going. 0 turns it off. Default 0.5.

LeadMax: number?--

Studs the lead is capped at. Default 8.

LeadMinDistance: number?--

Within this many studs of the target there is no lead, so an agent at attack range does not overshoot. Default 10.

FailedRetry: number?--

Seconds before a target that failed twice in a row is tried again, doubling for each further failure up to 4, instead of every RepathInterval. A target that moves is tried again at once. Default 1.

}

Tunes how Navigator turns repeated MoveTo calls into plans.

interface Navigator {
MoveTo: (
self: any,
target: Vector3
) → ()--

Head to target. Safe to call every tick; re-plans on a cadence.

Stop: (self: any) → ()--

Forget the target and halt.

GetStatus: (self: any) → PathStatus--

Where the navigator is with its target.

GetTarget: (self: any) → Vector3?--

The current target.

GetPath: (self: any) → Path?--

The last path planned.

StepReached: Signal<(
number,
)>--

Forwarded from the mover.

Arrived: Signal<boolean>--

Fired when the target is reached (true) or given up on (false).

PathChanged: Signal<Path?>--

Fired with each new path, and with nil when there is none.

IsDirect: (self: any) → boolean--

Whether the agent is walking straight at its target rather than following a plan.

GetRemainingDistance: (self: any) → number--

Studs left along the current path, math.huge when there is none.

}

Owns the pair of a Types.Pathfinder and a [Types.Mover]: plans with one, follows with the other, and re-plans when the target moves, the pathfinder reports the path blocked, or the path is older than MaxPathAge. Every agent with both has one at agent.Navigator.

AgentDefinition​

interface AgentDefinition {
Entity: Model?--

The model the AI drives. Needs a PrimaryPart. Leave it out for a headless agent, whose Mover has GetPivot (Record).

Brain: Brain--

The state table.

Pathfinder: Pathfinder?--

Required if Mover is given.

Mover: Mover?--

Required if Pathfinder is given.

Senses: {Sense}?--

Ticked in order before the brain each tick.

Blackboard: Blackboard?--

Initial blackboard values. Copied per agent.

Data: {[string]: any}?--

Initial agent.Data. Copied per agent. Yours to use.

Tick: number?--

Seconds between ticks. Default 0.1.

Navigation: NavigationOptions?--

Navigator tuning.

Steps: {[string]: StepHandler | StepRegistration}?--

What to do at labelled steps, by label. See Builder:OnStep.

Direct: Pathfinder?--

Checks whether the agent can walk straight at its target; while it can, nothing is planned. Must not yield. See Builder:UseDirect.

StrictBlackboard: boolean?--

Warn once when a state reads a blackboard key that looks like a typo of one a sense wrote (SeenTaget). For development; costs a metatable on every read. Default false.

Name: string?--

Used in messages. Default Entity.Name, or "Agent" with no entity.

}

Everything an Agent is built from. The Builder produces one of these; you can also write it as a table and hand it to VluxyAI.NewAgent. Validate checks it either way.

Agent​

interface Agent {
Entity: Model--

The model being driven. nil on a headless agent, though typed as a Model so rig code reads simply; library code goes through GetPivot.

Name: string--

For messages.

Blackboard: Blackboard--

Written by senses, read by states.

Data: {[string]: any}--

Free scratch space for your states.

Brain: Brain--

The state table.

Senses: {Sense}--

Ticked before the brain.

Navigator: Navigator?--

Present when the definition had a pathfinder and a mover.

Mover: Mover?--

The mover, if any.

Pathfinder: Pathfinder?--

The pathfinder, if any.

StateChanged: Signal<(
string,
string?
)>--

Fired with the new and previous state names.

Destroyed: Signal<()>--

Fired once, at the end of Destroy, so anything built around the agent can go with it.

Events: Signal<(
string,
any
)>--

Everything the agent announces through Agent:Emit; a Broadcaster relays it to clients.

GetState: (self: Agent) → string?--

The current state name, nil before Start.

SetState: (
self: Agent,
name: string
) → ()--

Force a transition, re-entering if already there.

MoveTo: (
self: Agent,
target: Vector3
) → ()--

Shorthand for Navigator:MoveTo. Errors without a navigator; warns and does nothing before Start.

Stop: (self: Agent) → ()--

Shorthand for Navigator:Stop. No-op without a navigator.

GetPosition: (self: Agent) → Vector3--

The mover's position, or the entity's pivot.

GetPivot: (self: Agent) → CFrame--

The entity's pivot, or the mover's on a headless agent.

HasArrived: (self: Agent) → boolean--

true while the navigator status is Arrived.

GetPathStatus: (self: Agent) → PathStatus--

The navigator status, or Idle without a navigator.

SetSpeed: (
self: Agent,
speed: number,
seconds: number?
) → ()--

The base speed; the mover gets it times the speed scale. With seconds, ramps there over the ticks. No-op without a mover.

SetSpeedScale: (
self: Agent,
scale: number
) → ()--

A multiplier layered on the base speed, kept across states. 1 is none.

GetSpeedScale: (self: Agent) → number
GetSpeed: (self: Agent) → number--

The base speed, before the scale.

StartTimer: (
self: Agent,
name: string,
seconds: number
) → ()--

Starts a named timer.

TimerDone: (
self: Agent,
name: string
) → boolean--

true once the timer ran out, or if it was never started.

TimerLeft: (
self: Agent,
name: string
) → number--

Seconds left on the timer, 0 when done.

TimeInState: (self: Agent) → number--

Seconds since the current state was entered, paused time excluded. 0 before Start.

Pause: (self: Agent) → ()--

Freeze: no ticks, no movement, timers on hold. For cutscenes, jumpscares and a director backing an enemy off.

Resume: (self: Agent) → ()--

Undo Pause. The navigator re-plans to its target on the next tick.

IsPaused: (self: Agent) → boolean
LookAt: (
self: Agent,
position: Vector3?
) → ()--

Turn toward position while standing still, nil to stop. Needs a mover with Face; otherwise does nothing.

Teleport: (
self: Agent,
position: Vector3
) → ()--

Put the body at position at once, dropping the current path. Uses the mover's Teleport, else the entity's PivotTo.

IsNew: (
self: Agent,
key: string
) → boolean--

true while Blackboard[key] is non-nil and differs from the value last passed to Acknowledge.

Acknowledge: (
self: Agent,
key: string
) → ()--

Marks the current Blackboard[key] as dealt with, so IsNew is false until it changes.

Step: (
self: Agent,
deltaTime: number
) → ()--

One tick, driven by hand. Tests use this.

Start: (
self: Agent,
manual: boolean?
) → Agent--

Enters the initial state and starts ticking on Heartbeat. With manual, you drive Step yourself.

IsRunning: (self: Agent) → boolean--

true between Start and Destroy.

IsDestroyed: (self: Agent) → boolean--

true once Destroy has run.

Emit: (
self: Agent,
name: string,
payload: any?
) → ()--

Announces something for clients: a scream, a kill, a door. Fires Events.

Destroy: (self: Agent) → ()--

Exits the current state, stops, destroys every part.

}

The running AI. Handed to every Enter, Update, Exit, When and Input function.

ClientState​

interface ClientState {
Enter: ((replica: any) → ())?--

Runs when the server enters the state of that name.

Update: ((
replica: any,
deltaTime: number
) → ())?--

Runs every frame while it is current.

Exit: ((replica: any) → ())?--

Runs when the server leaves it.

}

The client's side of one server state: what a Replica does while the enemy is in it. The same shape as a Types.State without transitions, since the server decides those.

Visuals​

type Visuals = {[string]: ClientState}

Client states keyed by server state name. Only the states a client needs to react to are listed; the rest are tracked without doing anything.

Snapshot​

interface Snapshot {
State: string?--

The current state name.

Position: Vector3--

The rig's pivot position.

Yaw: number--

Facing in radians about Y.

}

Everything a client that just joined needs, from Broadcaster.Snapshot to Replica.ApplySnapshot.

KillKind​

type KillKind = "Live" | "BlackBox" | "Scare"

How a Kill is presented: played out in the world, cut to black, or a scare, which is the live hold without the death.

HealthProvider​

interface HealthProvider {
Get: (character: Model) → number--

The character's current health.

Damage: (
character: Model,
amount: number
) → ()--

Hurt it.

Kill: ((character: Model) → ())?--

Finish it. Default: Damage with math.huge.

}

How Attack and Kill read and change a victim's health when the game does not use the Humanoid for it. Without one, the Humanoid's Health and TakeDamage are used.

KillEvent​

interface KillEvent {
Kind: KillKind
Victim: Model--

The character being killed.

Duration: number--

Seconds until the victim dies (or, for a scare, is released), for the client to match.

}

The payload of the "Kill" event an agent emits, and of the "Scare" event a scare emits. A "KillCancelled" (or "ScareCancelled") event with { Kind, Victim } follows if it ends early.

Show raw api
{
    "functions": [],
    "properties": [],
    "types": [
        {
            "name": "Connection",
            "desc": "What [Types.Signal] `Connect` returns. Any cleanup helper that calls `Disconnect` accepts it.",
            "fields": [
                {
                    "name": "Connected",
                    "lua_type": "boolean",
                    "desc": "`false` once disconnected."
                },
                {
                    "name": "Disconnect",
                    "lua_type": "(self: Connection) -> ()",
                    "desc": "Disconnects the handler."
                }
            ],
            "source": {
                "line": 42,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Signal<T...>",
            "desc": "A small signal with the same surface as `RBXScriptSignal`, implemented in plain Luau so the\ncore runs outside Roblox. Movers and agents expose these; a [Cleaner] releases their connections.",
            "fields": [
                {
                    "name": "Connect",
                    "lua_type": "(self: Signal<T...>, handler: (T...) -> ()) -> Connection",
                    "desc": "Runs `handler` on every fire."
                },
                {
                    "name": "Once",
                    "lua_type": "(self: Signal<T...>, handler: (T...) -> ()) -> Connection",
                    "desc": "Runs `handler` on the next fire only."
                },
                {
                    "name": "Wait",
                    "lua_type": "(self: Signal<T...>) -> T...",
                    "desc": "Yields until the next fire."
                },
                {
                    "name": "Fire",
                    "lua_type": "(self: Signal<T...>, T...) -> ()",
                    "desc": "Fires every connected handler."
                },
                {
                    "name": "DisconnectAll",
                    "lua_type": "(self: Signal<T...>) -> ()",
                    "desc": "Drops every handler."
                },
                {
                    "name": "Destroy",
                    "lua_type": "(self: Signal<T...>) -> ()",
                    "desc": "Drops every handler and refuses new ones."
                }
            ],
            "source": {
                "line": 61,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "NoiseSignal",
            "desc": "What a [Hearing] sense listens to: fired with a world position, an optional loudness (`1` by\ndefault), an optional kind (`\"Footstep\"`, `\"Door\"`) and an optional source, the Instance that\nmade the noise (a player's character, a rig), so a brain can tell whose noise it was.",
            "lua_type": "Signal<(Vector3, number?, string?, Instance?)>",
            "source": {
                "line": 78,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "StepAction",
            "desc": "What a [Types.Mover] does at a step. Mirrors `Enum.PathWaypointAction`. `Custom` steps come\nfrom `PathfindingModifier` labels or your own pathfinder; the mover walks to them like any other\nstep and the [Types.Navigator]'s `StepReached` tells you when it got there, so a door or a ladder\nis your code.",
            "lua_type": "\"Walk\" | \"Jump\" | \"Custom\"",
            "source": {
                "line": 93,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Step",
            "desc": "One point along a [Types.Path].",
            "fields": [
                {
                    "name": "Position",
                    "lua_type": "Vector3",
                    "desc": "Where to go, as a floor position. Movers add their own height."
                },
                {
                    "name": "Action",
                    "lua_type": "StepAction",
                    "desc": "What to do on the way there."
                },
                {
                    "name": "Label",
                    "lua_type": "string?",
                    "desc": "The `PathfindingModifier` label, or whatever your pathfinder attaches. A [Types.StepHandler] registered for it runs when the step is reached."
                },
                {
                    "name": "Instance",
                    "lua_type": "Instance?",
                    "desc": "What the step is about, when the pathfinder knows: the door on a [Graph] link."
                }
            ],
            "source": {
                "line": 106,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "StepHandler",
            "desc": "What happens at a labelled step: open the door, climb the ladder, play the vault. Registered\nwith [Builder:OnStep] under the step's `Label`. Runs in its own thread and may yield; the\nnavigator holds the mover at the step, reports `Interacting`, and walks the rest of the path\nwhen the handler returns. A new target or `Stop` while it runs abandons the path; the handler\nshould check [Agent:IsDestroyed] after a long yield.\n\nReturn `false` to say the step could not be taken (a locked door). The path is given up as\n`Failed`, and the pathfinder is told to `Close` that step for the seconds given as the second\nreturn (default `10`), so the next plan goes another way. Returning nothing or `true` means\nthe step was taken.",
            "lua_type": "(agent: Agent, step: Step, index: number) -> (boolean?, number?)",
            "source": {
                "line": 128,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "StepRegistration",
            "desc": "What [Builder:OnStep] stores for a label: the handler, how to find the door for a step that\ndoes not already know it, and how to tell that there is nothing to do. A plain\n[Types.StepHandler] is accepted wherever this is.",
            "fields": [
                {
                    "name": "Handler",
                    "lua_type": "StepHandler",
                    "desc": ""
                },
                {
                    "name": "Tag",
                    "lua_type": "string?",
                    "desc": "When the step arrives without an `Instance` (a navmesh link or modifier), the nearest instance carrying this `CollectionService` tag within `Radius` of the step is put in `step.Instance` before the handler runs."
                },
                {
                    "name": "Radius",
                    "lua_type": "number?",
                    "desc": "Studs. Default `8`."
                },
                {
                    "name": "Ready",
                    "lua_type": "((agent: Agent, step: Step) -> boolean)?",
                    "desc": "Asked first, after `Tag` has found the instance. `true` means the step needs nothing (the door is already open), so the agent walks on without stopping and the handler does not run."
                }
            ],
            "source": {
                "line": 143,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "DoorDriver",
            "desc": "How [Doors] opens a door in your game. The package never moves a door itself: your door\nscript does, and the driver is the few lines that connect the two. [Doors.Attributes] is a\nready-made driver that talks through attributes, for a door script that listens to them.",
            "fields": [
                {
                    "name": "IsOpen",
                    "lua_type": "(self: any, door: Instance) -> boolean",
                    "desc": "Open, or opening: an agent reaching it walks on without stopping."
                },
                {
                    "name": "Open",
                    "lua_type": "(self: any, door: Instance, agent: Agent) -> ()",
                    "desc": "Start opening it. Must not yield; the agent waits `OpenTime` after it."
                },
                {
                    "name": "IsLocked",
                    "lua_type": "((self: any, door: Instance, agent: Agent) -> boolean)?",
                    "desc": "Optional. A locked door is refused and planned around."
                },
                {
                    "name": "OpenTime",
                    "lua_type": "((self: any, door: Instance) -> number)?",
                    "desc": "Optional. Seconds the door takes to open, which the agent waits after `Open` before walking through. Default: [Doors.Step]'s `OpenTime`."
                }
            ],
            "source": {
                "line": 163,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Path",
            "desc": "An ordered list of steps from where the agent was to where it wants to be. The first step is the\nagent's own position when the pathfinder includes it; movers cope either way.",
            "lua_type": "{ Step }",
            "source": {
                "line": 177,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Pathfinder",
            "desc": "Plans and nothing else. A pathfinder never moves the agent, never creates parts and never keeps\na target. `from` and `to` may be at any height: `from` is the mover's root and `to` is often a\nplayer's root or a floor node; the path it returns holds floor positions. An empty path counts\nas a failure. `FindPath` runs in its own thread and may yield or error; an error is treated as a\nfailed plan and warned about once. `VluxyAI.Pathfinders` ships `Navmesh`, `Straight` and\n`Ladder`; your own is any table with `FindPath`.\n\n`Blocked` is how a pathfinder that watches the world ([Navmesh] listens to its `Path.Blocked`)\ntells the navigator that the path being walked is no longer good, so the agent re-plans as soon\nas the cadence allows instead of walking into the obstacle and waiting for the mover's stuck\ncheck. A pathfinder that cannot know leaves it out; [Ladder] and [Hybrid] forward it from the\nrung that produced the current path.",
            "fields": [
                {
                    "name": "FindPath",
                    "lua_type": "(self: any, from: Vector3, to: Vector3, agent: Agent) -> (Path?, string?)",
                    "desc": "Returns a path, or `nil` and a reason. May yield."
                },
                {
                    "name": "Destroy",
                    "lua_type": "((self: any) -> ())?",
                    "desc": "Optional cleanup. Called when the agent is destroyed."
                },
                {
                    "name": "Name",
                    "lua_type": "string?",
                    "desc": "Used in messages, such as a [Ladder]'s joined reasons."
                },
                {
                    "name": "Blocked",
                    "lua_type": "Signal<()>?",
                    "desc": "Optional. Fired when the world changes under the last path returned; the [Navigator] re-plans on its cadence."
                },
                {
                    "name": "Close",
                    "lua_type": "((self: any, step: Step, seconds: number) -> ())?",
                    "desc": "Optional. A labelled step could not be taken (a locked door): plan around it for `seconds`. [Graph] closes the link, [Navmesh] refuses paths through it; [Ladder] and [Hybrid] pass it on."
                }
            ],
            "source": {
                "line": 206,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Mover",
            "desc": "Executes a [Types.Path] and nothing else. Rules that keep a mover and the [Navigator] honest:\n\n- `Follow` fires `Arrived(false)` for the path it replaces before starting the new one, and\n  the new path's outcome is reported on a later frame, never from inside `Follow`. An empty\n  path is `Arrived(false)` on the next frame.\n- `Follow` is called from the navigator's planning thread, so it must not yield.\n- `Arrived(true)` means the last step was reached. The navigator then checks the target for\n  itself: within `ArriveDistance` it reports arrival, otherwise it re-plans or reports failure.\n- `Arrived(false)` from a stuck detector makes the navigator re-plan on its cadence.\n\n`VluxyAI.Movers` ships `Humanoid` (uses `MoveTo`), `CFrame` (steps a pivot, no rig needed) and\n`Record` (a pivot in a table, no Instance at all);\nyour own is any table with this shape. Make the signals with `VluxyAI.Signal.new()`.",
            "fields": [
                {
                    "name": "Follow",
                    "lua_type": "(self: any, path: Path) -> ()",
                    "desc": "Starts moving along `path`, replacing any current path."
                },
                {
                    "name": "Stop",
                    "lua_type": "(self: any) -> ()",
                    "desc": "Halts. Fires `Arrived(false)` if a path was in progress."
                },
                {
                    "name": "GetPosition",
                    "lua_type": "(self: any) -> Vector3",
                    "desc": "Where the agent's root or pivot is right now: the body, not the floor."
                },
                {
                    "name": "GetSpeed",
                    "lua_type": "(self: any) -> number",
                    "desc": "Studs per second."
                },
                {
                    "name": "SetSpeed",
                    "lua_type": "(self: any, speed: number) -> ()",
                    "desc": "Studs per second."
                },
                {
                    "name": "IsMoving",
                    "lua_type": "(self: any) -> boolean",
                    "desc": "`true` while a path is in progress."
                },
                {
                    "name": "StepReached",
                    "lua_type": "Signal<(number, Step)>",
                    "desc": "Fired with the index and step each time one is reached."
                },
                {
                    "name": "Arrived",
                    "lua_type": "Signal<boolean>",
                    "desc": "Fired once per `Follow`: `true` at the end of the path, `false` if stopped, replaced or stuck."
                },
                {
                    "name": "Destroy",
                    "lua_type": "((self: any) -> ())?",
                    "desc": "Optional cleanup. Called when the agent is destroyed."
                },
                {
                    "name": "Face",
                    "lua_type": "((self: any, position: Vector3?) -> ())?",
                    "desc": "Optional. Turn toward `position` while standing still; `nil` stops. [Agent:LookAt] calls it."
                },
                {
                    "name": "Teleport",
                    "lua_type": "((self: any, position: Vector3) -> ())?",
                    "desc": "Optional. Put the body at `position` at once, dropping any path. [Agent:Teleport] calls it."
                },
                {
                    "name": "SetNudge",
                    "lua_type": "((self: any, offset: Vector3?) -> ())?",
                    "desc": "Optional. Aim at each step plus `offset` (never the last step), so a steering sense can push the body sideways; `nil` clears. [Neighbours] calls it."
                },
                {
                    "name": "GetPivot",
                    "lua_type": "((self: any) -> CFrame)?",
                    "desc": "Optional. Position and facing. Required for an agent with no Entity: [Agent:GetPivot] reads it."
                },
                {
                    "name": "Step",
                    "lua_type": "((self: any, deltaTime: number) -> ())?",
                    "desc": "Optional. A mover with no clock of its own: [Agent:Step] calls it first, with the same delta time. Leave it out on a mover that runs on Heartbeat, or it moves twice."
                }
            ],
            "source": {
                "line": 251,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Blackboard",
            "desc": "The only thing states read about the world. Senses write to it every tick; states and utility\nconsiderations read it and never query the world themselves. Each sense documents the keys it\nwrites. Declare your own blackboard type per AI and cast once at the boundary.",
            "lua_type": "{ [string]: any }",
            "source": {
                "line": 280,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Sense",
            "desc": "Perceives and nothing else. Senses tick in the order they were added, before the navigator and\nthe brain. A sense keeps no per-agent state of its own: memory (`SeenAt`, `HeardAt`) lives on\nthe blackboard, so one instance may serve many agents and a state may clear a memory early by\nsetting the key to `nil`. Distances are `math.huge` when there is nothing to measure.\n`VluxyAI.Senses` ships `Sight`, `Proximity`, `Hearing`, `Sounds`, `Awareness`, `Watched`,\n`Surroundings` and `Neighbours`; your own is any table with `Tick`.",
            "fields": [
                {
                    "name": "Tick",
                    "lua_type": "(self: any, agent: Agent, deltaTime: number) -> ()",
                    "desc": "Called once per agent tick. Writes to `agent.Blackboard`."
                },
                {
                    "name": "Destroy",
                    "lua_type": "((self: any, agent: Agent?) -> ())?",
                    "desc": "Optional cleanup, called with each agent that is destroyed. A shared sense releases that agent's state and tears down only when none remain; called with no agent it tears down outright."
                },
                {
                    "name": "Configure",
                    "lua_type": "((self: any, patch: { [string]: any }) -> ())?",
                    "desc": "Optional. Changes options after construction; every built-in has it, so a director can widen a cone or shorten a memory mid-round."
                }
            ],
            "source": {
                "line": 297,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Transition",
            "desc": "A declarative transition. The first one whose `When` returns `true` wins. `To` may name the\ncurrent state, which re-enters it (`Exit` then `Enter`); an `Update` returning the current name\ndoes nothing. If `When` forces a change through [Agent:SetState], that change wins and its own\nresult is ignored.",
            "fields": [
                {
                    "name": "To",
                    "lua_type": "string",
                    "desc": "The state to enter. Must exist in the brain; `Build()` checks."
                },
                {
                    "name": "When",
                    "lua_type": "(agent: Agent) -> boolean",
                    "desc": "Checked every tick, in order, before `Update`."
                }
            ],
            "source": {
                "line": 319,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Interrupt",
            "desc": "A [Types.Transition] that applies from every state: \"hunt whoever I see\", \"freeze when lit\",\n\"stop when the director says so\". Written once on the brain instead of in every state.\nInterrupts are checked in order before the current state's transitions; the first true one\nwins the tick.",
            "fields": [
                {
                    "name": "To",
                    "lua_type": "string",
                    "desc": "The state to enter. Must exist in the brain; `Build()` checks."
                },
                {
                    "name": "When",
                    "lua_type": "(agent: Agent) -> boolean",
                    "desc": "Checked every tick, in order, before the current state's own transitions."
                },
                {
                    "name": "Except",
                    "lua_type": "{ string }?",
                    "desc": "States in which this interrupt is not checked, such as the one it leads to or an attack that must finish."
                }
            ],
            "source": {
                "line": 337,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "State",
            "desc": "One state of a [Types.Brain]. Every field is optional, so a state can be as small as a single\n`Update`. Returning the current state's name from `Update` does nothing; use [Agent:SetState] to\nre-enter a state on purpose. If `Update` itself forces a change through `SetState`, its return\nvalue is ignored so two transitions never stack.",
            "fields": [
                {
                    "name": "Enter",
                    "lua_type": "((agent: Agent) -> ())?",
                    "desc": "Runs once when the state becomes current."
                },
                {
                    "name": "Update",
                    "lua_type": "((agent: Agent, deltaTime: number) -> string?)?",
                    "desc": "Runs every tick. Return a state name to change state, or nothing to stay."
                },
                {
                    "name": "Exit",
                    "lua_type": "((agent: Agent) -> ())?",
                    "desc": "Runs once when the state stops being current."
                },
                {
                    "name": "Transitions",
                    "lua_type": "{ Transition }?",
                    "desc": "Checked in order before `Update` each tick."
                }
            ],
            "source": {
                "line": 357,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Brain",
            "desc": "A flat finite state machine as a table. The whole AI, readable top to bottom.",
            "fields": [
                {
                    "name": "Initial",
                    "lua_type": "string",
                    "desc": "The state entered on `Start()`."
                },
                {
                    "name": "States",
                    "lua_type": "{ [string]: State }",
                    "desc": "Every state, keyed by name."
                },
                {
                    "name": "Interrupts",
                    "lua_type": "{ Interrupt }?",
                    "desc": "Transitions that apply from every state, checked before the current state's own."
                }
            ],
            "source": {
                "line": 374,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Curve",
            "desc": "Maps a normalised input in `[0, 1]` to a score in `[0, 1]`. [Utility.Curves] ships the common\nshapes; any function works.",
            "lua_type": "(x: number) -> number",
            "source": {
                "line": 391,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Consideration<T>",
            "desc": "One factor in scoring a candidate. A candidate's score is the product of its considerations, so\nany one of them can veto by returning `0`.",
            "fields": [
                {
                    "name": "Input",
                    "lua_type": "(agent: Agent, candidate: T) -> number",
                    "desc": "Reads a raw value about the candidate, usually off the blackboard."
                },
                {
                    "name": "Min",
                    "lua_type": "number?",
                    "desc": "The raw value that maps to `0`. Default `0`."
                },
                {
                    "name": "Max",
                    "lua_type": "number?",
                    "desc": "The raw value that maps to `1`. Default `1`."
                },
                {
                    "name": "Curve",
                    "lua_type": "Curve?",
                    "desc": "The response curve. Default linear."
                },
                {
                    "name": "Invert",
                    "lua_type": "boolean?",
                    "desc": "Flip the normalised input before the curve, so a big distance scores low."
                }
            ],
            "source": {
                "line": 406,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Scored<T>",
            "desc": "One row of a [Utility.ScoreAll] result.",
            "fields": [
                {
                    "name": "Candidate",
                    "lua_type": "T",
                    "desc": "The thing that was scored."
                },
                {
                    "name": "Score",
                    "lua_type": "number",
                    "desc": "Its score in `[0, 1]`."
                }
            ],
            "source": {
                "line": 423,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "PathStatus",
            "desc": "Where a [Navigator] is with its current target. `Idle` means no target or stopped;\n`Interacting` means a [Types.StepHandler] is running at a step and the walk resumes after.",
            "lua_type": "\"Idle\" | \"Planning\" | \"Moving\" | \"Interacting\" | \"Arrived\" | \"Failed\"",
            "source": {
                "line": 439,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "NavigationOptions",
            "desc": "Tunes how [Navigator] turns repeated `MoveTo` calls into plans.",
            "fields": [
                {
                    "name": "RepathInterval",
                    "lua_type": "number?",
                    "desc": "Seconds between re-plans while the target keeps moving. Must be above 0; the three distances below may be 0. Default `0.5`."
                },
                {
                    "name": "RepathDistance",
                    "lua_type": "number?",
                    "desc": "Studs the target must move before a re-plan happens. Default `2`."
                },
                {
                    "name": "MaxPathAge",
                    "lua_type": "number?",
                    "desc": "Seconds a path is followed before it is planned again even though the target has not moved, so the agent keeps up with a changing level. Must be above 0. Default `math.huge` (never)."
                },
                {
                    "name": "ArriveDistance",
                    "lua_type": "number?",
                    "desc": "Horizontal studs from the target that count as arrived. Must exceed the two bodies' radii for a chase to ever arrive. Default `2.5`."
                },
                {
                    "name": "ArriveHeight",
                    "lua_type": "number?",
                    "desc": "Height difference allowed for arrival, so a target on the floor above does not count. Default `8`."
                },
                {
                    "name": "DirectInterval",
                    "lua_type": "number?",
                    "desc": "With a direct pathfinder ([Builder:UseDirect]): seconds between checks that the way to the target is still clear. Must be above 0. Default `0.15`."
                },
                {
                    "name": "DirectStuckTime",
                    "lua_type": "number?",
                    "desc": "Seconds walking direct without moving a stud before the navigator gives up on it and plans. Must be above 0. Default `1`."
                },
                {
                    "name": "DirectCooldown",
                    "lua_type": "number?",
                    "desc": "Seconds after giving up on a direct walk before trying one again. Default `3`."
                },
                {
                    "name": "Lead",
                    "lua_type": "number?",
                    "desc": "Seconds of a moving target's velocity to plan ahead of it, so a chase heads where the player is going. `0` turns it off. Default `0.5`."
                },
                {
                    "name": "LeadMax",
                    "lua_type": "number?",
                    "desc": "Studs the lead is capped at. Default `8`."
                },
                {
                    "name": "LeadMinDistance",
                    "lua_type": "number?",
                    "desc": "Within this many studs of the target there is no lead, so an agent at attack range does not overshoot. Default `10`."
                },
                {
                    "name": "FailedRetry",
                    "lua_type": "number?",
                    "desc": "Seconds before a target that failed twice in a row is tried again, doubling for each further failure up to 4, instead of every `RepathInterval`. A target that moves is tried again at once. Default `1`."
                }
            ],
            "source": {
                "line": 460,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Navigator",
            "desc": "Owns the pair of a [Types.Pathfinder] and a [Types.Mover]: plans with one, follows with the\nother, and re-plans when the target moves, the pathfinder reports the path blocked, or the path\nis older than `MaxPathAge`. Every agent with both has one at `agent.Navigator`.",
            "fields": [
                {
                    "name": "MoveTo",
                    "lua_type": "(self: any, target: Vector3) -> ()",
                    "desc": "Head to `target`. Safe to call every tick; re-plans on a cadence."
                },
                {
                    "name": "Stop",
                    "lua_type": "(self: any) -> ()",
                    "desc": "Forget the target and halt."
                },
                {
                    "name": "GetStatus",
                    "lua_type": "(self: any) -> PathStatus",
                    "desc": "Where the navigator is with its target."
                },
                {
                    "name": "GetTarget",
                    "lua_type": "(self: any) -> Vector3?",
                    "desc": "The current target."
                },
                {
                    "name": "GetPath",
                    "lua_type": "(self: any) -> Path?",
                    "desc": "The last path planned."
                },
                {
                    "name": "StepReached",
                    "lua_type": "Signal<(number, Step)>",
                    "desc": "Forwarded from the mover."
                },
                {
                    "name": "Arrived",
                    "lua_type": "Signal<boolean>",
                    "desc": "Fired when the target is reached (`true`) or given up on (`false`)."
                },
                {
                    "name": "PathChanged",
                    "lua_type": "Signal<Path?>",
                    "desc": "Fired with each new path, and with `nil` when there is none."
                },
                {
                    "name": "IsDirect",
                    "lua_type": "(self: any) -> boolean",
                    "desc": "Whether the agent is walking straight at its target rather than following a plan."
                },
                {
                    "name": "GetRemainingDistance",
                    "lua_type": "(self: any) -> number",
                    "desc": "Studs left along the current path, `math.huge` when there is none."
                }
            ],
            "source": {
                "line": 494,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "AgentDefinition",
            "desc": "Everything an [Agent] is built from. The [Builder] produces one of these; you can also write it\nas a table and hand it to `VluxyAI.NewAgent`. `Validate` checks it either way.",
            "fields": [
                {
                    "name": "Entity",
                    "lua_type": "Model?",
                    "desc": "The model the AI drives. Needs a `PrimaryPart`. Leave it out for a headless agent, whose `Mover` has `GetPivot` ([Record])."
                },
                {
                    "name": "Brain",
                    "lua_type": "Brain",
                    "desc": "The state table."
                },
                {
                    "name": "Pathfinder",
                    "lua_type": "Pathfinder?",
                    "desc": "Required if `Mover` is given."
                },
                {
                    "name": "Mover",
                    "lua_type": "Mover?",
                    "desc": "Required if `Pathfinder` is given."
                },
                {
                    "name": "Senses",
                    "lua_type": "{ Sense }?",
                    "desc": "Ticked in order before the brain each tick."
                },
                {
                    "name": "Blackboard",
                    "lua_type": "Blackboard?",
                    "desc": "Initial blackboard values. Copied per agent."
                },
                {
                    "name": "Data",
                    "lua_type": "{ [string]: any }?",
                    "desc": "Initial `agent.Data`. Copied per agent. Yours to use."
                },
                {
                    "name": "Tick",
                    "lua_type": "number?",
                    "desc": "Seconds between ticks. Default `0.1`."
                },
                {
                    "name": "Navigation",
                    "lua_type": "NavigationOptions?",
                    "desc": "Navigator tuning."
                },
                {
                    "name": "Steps",
                    "lua_type": "{ [string]: StepHandler | StepRegistration }?",
                    "desc": "What to do at labelled steps, by label. See [Builder:OnStep]."
                },
                {
                    "name": "Direct",
                    "lua_type": "Pathfinder?",
                    "desc": "Checks whether the agent can walk straight at its target; while it can, nothing is planned. Must not yield. See [Builder:UseDirect]."
                },
                {
                    "name": "StrictBlackboard",
                    "lua_type": "boolean?",
                    "desc": "Warn once when a state reads a blackboard key that looks like a typo of one a sense wrote (`SeenTaget`). For development; costs a metatable on every read. Default `false`."
                },
                {
                    "name": "Name",
                    "lua_type": "string?",
                    "desc": "Used in messages. Default `Entity.Name`, or `\"Agent\"` with no entity."
                }
            ],
            "source": {
                "line": 532,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Agent",
            "desc": "The running AI. Handed to every `Enter`, `Update`, `Exit`, `When` and `Input` function.",
            "fields": [
                {
                    "name": "Entity",
                    "lua_type": "Model",
                    "desc": "The model being driven. `nil` on a headless agent, though typed as a Model so rig code reads simply; library code goes through `GetPivot`."
                },
                {
                    "name": "Name",
                    "lua_type": "string",
                    "desc": "For messages."
                },
                {
                    "name": "Blackboard",
                    "lua_type": "Blackboard",
                    "desc": "Written by senses, read by states."
                },
                {
                    "name": "Data",
                    "lua_type": "{ [string]: any }",
                    "desc": "Free scratch space for your states."
                },
                {
                    "name": "Brain",
                    "lua_type": "Brain",
                    "desc": "The state table."
                },
                {
                    "name": "Senses",
                    "lua_type": "{ Sense }",
                    "desc": "Ticked before the brain."
                },
                {
                    "name": "Navigator",
                    "lua_type": "Navigator?",
                    "desc": "Present when the definition had a pathfinder and a mover."
                },
                {
                    "name": "Mover",
                    "lua_type": "Mover?",
                    "desc": "The mover, if any."
                },
                {
                    "name": "Pathfinder",
                    "lua_type": "Pathfinder?",
                    "desc": "The pathfinder, if any."
                },
                {
                    "name": "StateChanged",
                    "lua_type": "Signal<(string, string?)>",
                    "desc": "Fired with the new and previous state names."
                },
                {
                    "name": "Destroyed",
                    "lua_type": "Signal<()>",
                    "desc": "Fired once, at the end of `Destroy`, so anything built around the agent can go with it."
                },
                {
                    "name": "Events",
                    "lua_type": "Signal<(string, any)>",
                    "desc": "Everything the agent announces through [Agent:Emit]; a [Broadcaster] relays it to clients."
                },
                {
                    "name": "GetState",
                    "lua_type": "(self: Agent) -> string?",
                    "desc": "The current state name, `nil` before `Start`."
                },
                {
                    "name": "SetState",
                    "lua_type": "(self: Agent, name: string) -> ()",
                    "desc": "Force a transition, re-entering if already there."
                },
                {
                    "name": "MoveTo",
                    "lua_type": "(self: Agent, target: Vector3) -> ()",
                    "desc": "Shorthand for `Navigator:MoveTo`. Errors without a navigator; warns and does nothing before `Start`."
                },
                {
                    "name": "Stop",
                    "lua_type": "(self: Agent) -> ()",
                    "desc": "Shorthand for `Navigator:Stop`. No-op without a navigator."
                },
                {
                    "name": "GetPosition",
                    "lua_type": "(self: Agent) -> Vector3",
                    "desc": "The mover's position, or the entity's pivot."
                },
                {
                    "name": "GetPivot",
                    "lua_type": "(self: Agent) -> CFrame",
                    "desc": "The entity's pivot, or the mover's on a headless agent."
                },
                {
                    "name": "HasArrived",
                    "lua_type": "(self: Agent) -> boolean",
                    "desc": "`true` while the navigator status is `Arrived`."
                },
                {
                    "name": "GetPathStatus",
                    "lua_type": "(self: Agent) -> PathStatus",
                    "desc": "The navigator status, or `Idle` without a navigator."
                },
                {
                    "name": "SetSpeed",
                    "lua_type": "(self: Agent, speed: number, seconds: number?) -> ()",
                    "desc": "The base speed; the mover gets it times the speed scale. With `seconds`, ramps there over the ticks. No-op without a mover."
                },
                {
                    "name": "SetSpeedScale",
                    "lua_type": "(self: Agent, scale: number) -> ()",
                    "desc": "A multiplier layered on the base speed, kept across states. `1` is none."
                },
                {
                    "name": "GetSpeedScale",
                    "lua_type": "(self: Agent) -> number",
                    "desc": ""
                },
                {
                    "name": "GetSpeed",
                    "lua_type": "(self: Agent) -> number",
                    "desc": "The base speed, before the scale."
                },
                {
                    "name": "StartTimer",
                    "lua_type": "(self: Agent, name: string, seconds: number) -> ()",
                    "desc": "Starts a named timer."
                },
                {
                    "name": "TimerDone",
                    "lua_type": "(self: Agent, name: string) -> boolean",
                    "desc": "`true` once the timer ran out, or if it was never started."
                },
                {
                    "name": "TimerLeft",
                    "lua_type": "(self: Agent, name: string) -> number",
                    "desc": "Seconds left on the timer, `0` when done."
                },
                {
                    "name": "TimeInState",
                    "lua_type": "(self: Agent) -> number",
                    "desc": "Seconds since the current state was entered, paused time excluded. `0` before `Start`."
                },
                {
                    "name": "Pause",
                    "lua_type": "(self: Agent) -> ()",
                    "desc": "Freeze: no ticks, no movement, timers on hold. For cutscenes, jumpscares and a director backing an enemy off."
                },
                {
                    "name": "Resume",
                    "lua_type": "(self: Agent) -> ()",
                    "desc": "Undo `Pause`. The navigator re-plans to its target on the next tick."
                },
                {
                    "name": "IsPaused",
                    "lua_type": "(self: Agent) -> boolean",
                    "desc": ""
                },
                {
                    "name": "LookAt",
                    "lua_type": "(self: Agent, position: Vector3?) -> ()",
                    "desc": "Turn toward `position` while standing still, `nil` to stop. Needs a mover with `Face`; otherwise does nothing."
                },
                {
                    "name": "Teleport",
                    "lua_type": "(self: Agent, position: Vector3) -> ()",
                    "desc": "Put the body at `position` at once, dropping the current path. Uses the mover's `Teleport`, else the entity's `PivotTo`."
                },
                {
                    "name": "IsNew",
                    "lua_type": "(self: Agent, key: string) -> boolean",
                    "desc": "`true` while `Blackboard[key]` is non-nil and differs from the value last passed to `Acknowledge`."
                },
                {
                    "name": "Acknowledge",
                    "lua_type": "(self: Agent, key: string) -> ()",
                    "desc": "Marks the current `Blackboard[key]` as dealt with, so `IsNew` is `false` until it changes."
                },
                {
                    "name": "Step",
                    "lua_type": "(self: Agent, deltaTime: number) -> ()",
                    "desc": "One tick, driven by hand. Tests use this."
                },
                {
                    "name": "Start",
                    "lua_type": "(self: Agent, manual: boolean?) -> Agent",
                    "desc": "Enters the initial state and starts ticking on `Heartbeat`. With `manual`, you drive `Step` yourself."
                },
                {
                    "name": "IsRunning",
                    "lua_type": "(self: Agent) -> boolean",
                    "desc": "`true` between `Start` and `Destroy`."
                },
                {
                    "name": "IsDestroyed",
                    "lua_type": "(self: Agent) -> boolean",
                    "desc": "`true` once `Destroy` has run."
                },
                {
                    "name": "Emit",
                    "lua_type": "(self: Agent, name: string, payload: any?) -> ()",
                    "desc": "Announces something for clients: a scream, a kill, a door. Fires `Events`."
                },
                {
                    "name": "Destroy",
                    "lua_type": "(self: Agent) -> ()",
                    "desc": "Exits the current state, stops, destroys every part."
                }
            ],
            "source": {
                "line": 596,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "ClientState",
            "desc": "The client's side of one server state: what a [Replica] does while the enemy is in it. The\nsame shape as a [Types.State] without transitions, since the server decides those.",
            "fields": [
                {
                    "name": "Enter",
                    "lua_type": "((replica: any) -> ())?",
                    "desc": "Runs when the server enters the state of that name."
                },
                {
                    "name": "Update",
                    "lua_type": "((replica: any, deltaTime: number) -> ())?",
                    "desc": "Runs every frame while it is current."
                },
                {
                    "name": "Exit",
                    "lua_type": "((replica: any) -> ())?",
                    "desc": "Runs when the server leaves it."
                }
            ],
            "source": {
                "line": 656,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Visuals",
            "desc": "Client states keyed by server state name. Only the states a client needs to react to are\nlisted; the rest are tracked without doing anything.",
            "lua_type": "{ [string]: ClientState }",
            "source": {
                "line": 669,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "Snapshot",
            "desc": "Everything a client that just joined needs, from [Broadcaster.Snapshot] to [Replica.ApplySnapshot].",
            "fields": [
                {
                    "name": "State",
                    "lua_type": "string?",
                    "desc": "The current state name."
                },
                {
                    "name": "Position",
                    "lua_type": "Vector3",
                    "desc": "The rig's pivot position."
                },
                {
                    "name": "Yaw",
                    "lua_type": "number",
                    "desc": "Facing in radians about Y."
                }
            ],
            "source": {
                "line": 681,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "KillKind",
            "desc": "How a [Kill] is presented: played out in the world, cut to black, or a scare, which is the\nlive hold without the death.",
            "lua_type": "\"Live\" | \"BlackBox\" | \"Scare\"",
            "source": {
                "line": 694,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "HealthProvider",
            "desc": "How [Attack] and [Kill] read and change a victim's health when the game does not use the\n`Humanoid` for it. Without one, the Humanoid's `Health` and `TakeDamage` are used.",
            "fields": [
                {
                    "name": "Get",
                    "lua_type": "(character: Model) -> number",
                    "desc": "The character's current health."
                },
                {
                    "name": "Damage",
                    "lua_type": "(character: Model, amount: number) -> ()",
                    "desc": "Hurt it."
                },
                {
                    "name": "Kill",
                    "lua_type": "((character: Model) -> ())?",
                    "desc": "Finish it. Default: `Damage` with `math.huge`."
                }
            ],
            "source": {
                "line": 707,
                "path": "lib/Types.luau"
            }
        },
        {
            "name": "KillEvent",
            "desc": "The payload of the `\"Kill\"` event an agent emits, and of the `\"Scare\"` event a scare emits.\nA `\"KillCancelled\"` (or `\"ScareCancelled\"`) event with `{ Kind, Victim }` follows if it ends\nearly.",
            "fields": [
                {
                    "name": "Kind",
                    "lua_type": "KillKind",
                    "desc": ""
                },
                {
                    "name": "Victim",
                    "lua_type": "Model",
                    "desc": "The character being killed."
                },
                {
                    "name": "Duration",
                    "lua_type": "number",
                    "desc": "Seconds until the victim dies (or, for a scare, is released), for the client to match."
                }
            ],
            "source": {
                "line": 725,
                "path": "lib/Types.luau"
            }
        }
    ],
    "name": "Types",
    "desc": "The contracts VluxyAI is built from. You extend the package by passing in your own module that\nsatisfies one of these types; the package never learns your module exists.\n\nThe important ones:\n\n- [Types.Brain]: a table of states. The part you write for every AI.\n- [Types.Pathfinder]: anything with `FindPath`. Plans, never moves.\n- [Types.Mover]: anything with `Follow` and `Stop`. Moves, never plans.\n- [Types.Sense]: anything with `Tick` that writes to the [Types.Blackboard].\n- [Types.AgentDefinition]: the composition of the above, which `Build()` validates and turns into\n  an [Agent].",
    "source": {
        "line": 27,
        "path": "lib/Types.luau"
    }
}