Skip to main content

BulletContext

The public-facing object that weapon code interacts with for every in-flight projectile.

Create one before firing, pass it to Vetra:Fire, then use the solver's signals to react to events. The context is passed as the first argument on every signal so you can identify the bullet and read its current state.

local context = BulletContext.new({
    Origin    = muzzlePosition,
    Direction = direction,
    Speed     = 200,
})

Solver:Fire(context, Behavior)
UserData

Attach weapon-specific metadata (shooter UserId, damage value, hit-group flags, etc.) via context.UserData before calling Fire. This table is passed unchanged on every signal emission.

Properties

Id

This item is read only and cannot be modified. Read Only
BulletContext.Id: number

Auto-incrementing unique identifier assigned at construction time. Use this to distinguish bullets in signal handlers without storing separate references.

Origin

This item is read only and cannot be modified. Read Only
BulletContext.Origin: Vector3

World-space muzzle position at the moment the bullet was fired. Never changes.

Direction

This item is read only and cannot be modified. Read Only
BulletContext.Direction: Vector3

Unit direction vector the bullet was fired along. Never changes.

Speed

This item is read only and cannot be modified. Read Only
BulletContext.Speed: number

Initial speed in studs per second. The bullet's actual speed decreases over time due to drag, bounces (restitution), and pierces (speed retention). Read the magnitude of Velocity for the current speed.

StartTime

This item is read only and cannot be modified. Read Only
BulletContext.StartTime: number

os.clock() timestamp at construction. Use with BulletContext:GetLifetime to compute bullet age without storing the time yourself.

RaycastParams

This item is read only and cannot be modified. Read Only
BulletContext.RaycastParams: RaycastParams?

Optional per-bullet raycast filter. When set, used as the raycast params for this bullet if the Behavior does not have its own RaycastParams set.

Priority order in [Vetra:Fire]:

  1. BulletContext.RaycastParams, this field (highest priority).
  2. Behavior.RaycastParams, if the user called :RaycastParams() on the BehaviorBuilder.
  3. Default empty RaycastParams.new().

Useful when you share a single Behavior across many bullet types but need per-bullet filtering (e.g. different ignore lists per shooter).

Position

This item is read only and cannot be modified. Read Only
BulletContext.Position: Vector3?

Current world-space position. Updated every frame by the solver. nil until the first simulation step.

Velocity

This item is read only and cannot be modified. Read Only
BulletContext.Velocity: Vector3

Current velocity vector in studs per second. Updated every frame by the solver. The magnitude of this vector is the current speed.

Alive

This item is read only and cannot be modified. Read Only
BulletContext.Alive: boolean

true while the cast is being simulated. Set to false by BulletContext:Terminate or automatically by the solver on hit, distance expiry, speed expiry, or corner-trap detection.

Length

This item is read only and cannot be modified. Read Only
BulletContext.Length: number

True accumulated path distance in studs, the sum of every frame displacement since firing. This diverges from straight-line (Position - Origin).Magnitude for bullets that bounce or follow homing curves. See also BulletContext:GetDistanceTraveled.

SimulationTime

This item is read only and cannot be modified. Read Only
BulletContext.SimulationTime: number

Total seconds this bullet has been simulated, as tracked by the solver. Updated every frame. Use BulletContext:GetLifetime to read this. Does not advance while the cast is paused.

CosmeticBulletObject

This item is read only and cannot be modified. Read Only
BulletContext.CosmeticBulletObject: Instance?

Set by the solver after the cosmetic bullet is created. Readable from signal handlers (OnSegmentOpen, OnBounce, etc.) via the context argument. nil when no cosmetic is configured or after the bullet terminates.

UserData

BulletContext.UserData: {[any]any}

Free-form table for attaching cast-specific metadata (shooter UserId, weapon type, damage value, etc.). Surfaced on every signal emission — read it in your handlers to route events without maintaining a parallel lookup.

context.UserData.Damage    = 45
context.UserData.ShooterId = Players.LocalPlayer.UserId

Signals.OnHit:Connect(function(context, result, velocity)
    local damage    = context.UserData.Damage
    local shooterId = context.UserData.ShooterId
end)

FireTravelEvents

BulletContext.FireTravelEvents: boolean?

Controls whether OnTravel and OnTravelBatch fire for this bullet. Only relevant for the parallel solver — FF casts (no callbacks, no homing) default to false and skip travel signal emission entirely for performance. Set to true to opt in when you need per-frame position updates on a parallel cast.

Has no effect on the serial solver (Vetra.new()), which always fires travel signals.

Functions

new

BulletContext.new(configBulletContextConfig) → BulletContext

Creates a new BulletContext.

Origin, Direction, and Speed are required.

Optional config fields:

  • UserData — pre-populate the context's UserData table at construction.
  • RaycastParams — per-bullet raycast filter (highest priority over Behavior params).
  • FireTravelEvents — set false to suppress OnTravel/OnTravelBatch for this bullet (parallel solver only, defaults to false for FF casts).
  • SolverData — reserved for internal solver use, do not supply from weapon code.

IsAlive

BulletContext:IsAlive() → boolean

Returns whether the bullet is still alive and being simulated.

GetLifetime

BulletContext:GetLifetime() → number--

Simulated age in seconds.

Returns how many seconds this bullet has been simulated.

Tracks SimulationTime accumulated by the solver, not real-world clock time. Does not advance while the cast is paused.

GetDistanceTraveled

BulletContext:GetDistanceTraveled() → number--

Path length in studs.

Returns the true accumulated path length of the bullet in studs.

Unlike (Position - Origin).Magnitude, this correctly accounts for bounces and homing curves by accumulating the actual frame-by-frame displacement. Equivalent to reading context.Length.

GetSnapshot

BulletContext:GetSnapshot() → BulletSnapshot

Returns a read-only snapshot of the bullet's current state.

Useful for logging, replication, or passing state to systems that should not hold a live reference to the context.

Terminate

BulletContext:Terminate() → ()

Terminates the bullet immediately, notifying the solver to clean up resources, destroy the cosmetic object, and fire OnTerminated.

Calling Terminate on an already-dead bullet is a no-op.

GetCast

BulletContext:GetCast() → Cast?

Returns the internal Cast object associated with this bullet.

Available from OnFire onwards — nil before Fire() has been called. Use this to call Cast methods (SetVelocity, Pause, Terminate, etc.) directly from signal handlers.

Signals.OnPierce:Connect(function(context, result, velocity, pierceCount)
    if pierceCount >= 3 then
        context:GetCast():Terminate()
    end
end)
Show raw api
{
    "functions": [
        {
            "name": "new",
            "desc": "Creates a new BulletContext.\n\n`Origin`, `Direction`, and `Speed` are required.\n\nOptional config fields:\n- `UserData` — pre-populate the context's UserData table at construction.\n- `RaycastParams` — per-bullet raycast filter (highest priority over Behavior params).\n- `FireTravelEvents` — set `false` to suppress `OnTravel`/`OnTravelBatch` for this bullet (parallel solver only, defaults to `false` for FF casts).\n- `SolverData` — reserved for internal solver use, do not supply from weapon code.",
            "params": [
                {
                    "name": "config",
                    "desc": "",
                    "lua_type": "BulletContextConfig"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "BulletContext"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 202,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "IsAlive",
            "desc": "Returns whether the bullet is still alive and being simulated.",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 211,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "GetLifetime",
            "desc": "Returns how many seconds this bullet has been simulated.\n\nTracks `SimulationTime` accumulated by the solver, not real-world clock\ntime. Does not advance while the cast is paused.",
            "params": [],
            "returns": [
                {
                    "desc": "Simulated age in seconds.",
                    "lua_type": "number"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 221,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "GetDistanceTraveled",
            "desc": "Returns the true accumulated path length of the bullet in studs.\n\nUnlike `(Position - Origin).Magnitude`, this correctly accounts for\nbounces and homing curves by accumulating the actual frame-by-frame\ndisplacement. Equivalent to reading `context.Length`.",
            "params": [],
            "returns": [
                {
                    "desc": "Path length in studs.",
                    "lua_type": "number"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 232,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "GetSnapshot",
            "desc": "Returns a read-only snapshot of the bullet's current state.\n\nUseful for logging, replication, or passing state to systems that\nshould not hold a live reference to the context.",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "BulletSnapshot"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 242,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "Terminate",
            "desc": "Terminates the bullet immediately, notifying the solver to clean up\nresources, destroy the cosmetic object, and fire `OnTerminated`.\n\nCalling `Terminate` on an already-dead bullet is a no-op.",
            "params": [],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 250,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "GetCast",
            "desc": "Returns the internal Cast object associated with this bullet.\n\nAvailable from `OnFire` onwards — `nil` before `Fire()` has been called.\nUse this to call Cast methods (`SetVelocity`, `Pause`, `Terminate`, etc.)\ndirectly from signal handlers.\n\n```lua\nSignals.OnPierce:Connect(function(context, result, velocity, pierceCount)\n    if pierceCount >= 3 then\n        context:GetCast():Terminate()\n    end\nend)\n```",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Cast?"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 269,
                "path": "docs/BulletContext.lua"
            }
        }
    ],
    "properties": [
        {
            "name": "Id",
            "desc": "Auto-incrementing unique identifier assigned at construction time.\nUse this to distinguish bullets in signal handlers without storing\nseparate references.",
            "lua_type": "number",
            "readonly": true,
            "source": {
                "line": 41,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "Origin",
            "desc": "World-space muzzle position at the moment the bullet was fired. Never changes.",
            "lua_type": "Vector3",
            "readonly": true,
            "source": {
                "line": 49,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "Direction",
            "desc": "Unit direction vector the bullet was fired along. Never changes.",
            "lua_type": "Vector3",
            "readonly": true,
            "source": {
                "line": 57,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "Speed",
            "desc": "Initial speed in studs per second. The bullet's actual speed decreases\nover time due to drag, bounces (restitution), and pierces (speed retention).\nRead the magnitude of `Velocity` for the current speed.",
            "lua_type": "number",
            "readonly": true,
            "source": {
                "line": 67,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "StartTime",
            "desc": "`os.clock()` timestamp at construction. Use with [BulletContext:GetLifetime]\nto compute bullet age without storing the time yourself.",
            "lua_type": "number",
            "readonly": true,
            "source": {
                "line": 76,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "RaycastParams",
            "desc": "Optional per-bullet raycast filter. When set, used as the raycast params\nfor this bullet if the Behavior does not have its own `RaycastParams` set.\n\nPriority order in [Vetra:Fire]:\n1. `BulletContext.RaycastParams`, this field (highest priority).\n2. `Behavior.RaycastParams`, if the user called `:RaycastParams()` on the BehaviorBuilder.\n3. Default empty `RaycastParams.new()`.\n\nUseful when you share a single Behavior across many bullet types but need\nper-bullet filtering (e.g. different ignore lists per shooter).",
            "lua_type": "RaycastParams?",
            "readonly": true,
            "source": {
                "line": 93,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "Position",
            "desc": "Current world-space position. Updated every frame by the solver.\n`nil` until the first simulation step.",
            "lua_type": "Vector3?",
            "readonly": true,
            "source": {
                "line": 102,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "Velocity",
            "desc": "Current velocity vector in studs per second. Updated every frame by the\nsolver. The magnitude of this vector is the current speed.",
            "lua_type": "Vector3",
            "readonly": true,
            "source": {
                "line": 111,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "Alive",
            "desc": "`true` while the cast is being simulated. Set to `false` by\n[BulletContext:Terminate] or automatically by the solver on hit,\ndistance expiry, speed expiry, or corner-trap detection.",
            "lua_type": "boolean",
            "readonly": true,
            "source": {
                "line": 121,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "Length",
            "desc": "True accumulated path distance in studs, the sum of every frame\ndisplacement since firing. This diverges from straight-line\n`(Position - Origin).Magnitude` for bullets that bounce or follow\nhoming curves. See also [BulletContext:GetDistanceTraveled].",
            "lua_type": "number",
            "readonly": true,
            "source": {
                "line": 132,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "SimulationTime",
            "desc": "Total seconds this bullet has been simulated, as tracked by the solver.\nUpdated every frame. Use [BulletContext:GetLifetime] to read this.\nDoes not advance while the cast is paused.",
            "lua_type": "number",
            "readonly": true,
            "source": {
                "line": 142,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "CosmeticBulletObject",
            "desc": "Set by the solver after the cosmetic bullet is created. Readable from\nsignal handlers (`OnSegmentOpen`, `OnBounce`, etc.) via the context argument.\n`nil` when no cosmetic is configured or after the bullet terminates.",
            "lua_type": "Instance?",
            "readonly": true,
            "source": {
                "line": 152,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "UserData",
            "desc": "Free-form table for attaching cast-specific metadata (shooter UserId,\nweapon type, damage value, etc.). Surfaced on every signal emission —\nread it in your handlers to route events without maintaining a parallel lookup.\n\n```lua\ncontext.UserData.Damage    = 45\ncontext.UserData.ShooterId = Players.LocalPlayer.UserId\n\nSignals.OnHit:Connect(function(context, result, velocity)\n    local damage    = context.UserData.Damage\n    local shooterId = context.UserData.ShooterId\nend)\n```",
            "lua_type": "{[any]: any}",
            "source": {
                "line": 171,
                "path": "docs/BulletContext.lua"
            }
        },
        {
            "name": "FireTravelEvents",
            "desc": "Controls whether `OnTravel` and `OnTravelBatch` fire for this bullet.\nOnly relevant for the parallel solver — FF casts (no callbacks, no homing)\ndefault to `false` and skip travel signal emission entirely for performance.\nSet to `true` to opt in when you need per-frame position updates on a\nparallel cast.\n\nHas no effect on the serial solver (`Vetra.new()`), which always fires\ntravel signals.",
            "lua_type": "boolean?",
            "source": {
                "line": 185,
                "path": "docs/BulletContext.lua"
            }
        }
    ],
    "types": [],
    "name": "BulletContext",
    "desc": "The public-facing object that weapon code interacts with for every\nin-flight projectile.\n\nCreate one before firing, pass it to [Vetra:Fire], then use the\nsolver's signals to react to events. The context is passed as the\nfirst argument on every signal so you can identify the bullet and\nread its current state.\n\n```lua\nlocal context = BulletContext.new({\n    Origin    = muzzlePosition,\n    Direction = direction,\n    Speed     = 200,\n})\n\nSolver:Fire(context, Behavior)\n```\n\n:::tip UserData\nAttach weapon-specific metadata (shooter UserId, damage value, hit-group\nflags, etc.) via `context.UserData` before calling `Fire`. This table is\npassed unchanged on every signal emission.\n:::",
    "source": {
        "line": 28,
        "path": "docs/BulletContext.lua"
    }
}