Prediction ā
TSUKI has a built-in prediction engine for projectile leading and motion tracking.
Units ā
All spatial values are in game units (Source inches). Speeds are game-units/second (u/s). Multiply by 0.0254 to convert to meters / m/s.
Projectile Intercept ā
prediction.solve_linear(target, [source_pos,] speed, [bone], [extra_time]) ā
Solves straight-line projectile intercept: where to aim so a projectile of speed hits target, accounting for velocity and travel time.
| Param | Type | Description |
|---|---|---|
target | player | Player object to intercept |
source_pos | vec3 | Projectile origin. Defaults to local player's eye position if omitted |
speed | number | Projectile speed in u/s |
bone | string | Aim bone (e.g. "head"). Default = origin |
extra_time | number | Extra lead time in seconds for cast windup + latency |
bone and extra_time are order-flexible: a string is read as the bone, a number as extra_time.
Returns aim_pos: vec3, flight_time: number. Returns nil, nil if unreachable.
local me = local_player()
local speed = me:get_active_projectile_speed()
if speed <= 0 then return end
local aim, flight = prediction.solve_linear(target, speed, "head", 0.06)
if aim then
-- aim = world position to put the crosshair on
-- flight = seconds of travel
endWith explicit source position:
local origin = me:get_head_world()
local aim, flight = prediction.solve_linear(target, origin, speed, "head")prediction.solve_ballistic(target, [source_pos,] speed, gravity, up_speed, [bone], [extra_time]) ā
Work in progress
Arced/gravity projectile variant. Currently untested.
Solves an arced projectile intercept accounting for gravity. Same return signature as solve_linear.
Raw Prediction ā
prediction.predict(target, time, [bone]) ā
Raw position extrapolation: where target will be in time seconds. Returns a vec3. Returns (0,0,0) if unavailable.
local future_pos = prediction.predict(target, 0.5, "chest")prediction.get_velocity(target) ā
Returns the target's tracked velocity and acceleration as two vec3 values (u/s and u/s²).
local vel, accel = prediction.get_velocity(target)
local speed = vel:distance(vec3(0, 0, 0)) -- scalar speedprediction.get_state(target) ā
Movement-state snapshot. Returns a table:
| Field | Type | Description |
|---|---|---|
grounded | bool | On the ground |
airborne | bool | Not grounded (jumping / falling / in the air) |
stationary | bool | Grounded and barely moving |
speed | number | Total velocity magnitude (u/s) |
speed2d | number | Horizontal velocity magnitude (u/s) |
vel_z | number | Vertical velocity (u/s, negative = falling) |
Confidence ā
get_confidence tells you how predictable a target's motion is.
prediction.get_confidence(target, [ramp_seconds]) ā
Returns a table:
| Field | Type | Description |
|---|---|---|
stability | 0-1 | How steady the velocity is. Low = erratic |
straightness | 0-1 | How straight the path is. Low = turning / strafing |
decel | 0-1 | Deceleration factor. Low = stopping |
overall | 0-1 | min(stability, straightness) * decel |
center | vec3 | For strafing targets, the centroid of the oscillation in the horizontal plane; only x/y are meaningful - z is always 0 |
swing_radius | number | Max distance from the centroid (orbit radius, in game units) |
ramp_seconds (optional): how quickly confidence ramps up after motion stabilizes.
local c = prediction.get_confidence(target)
if c.overall >= 0.90 then
aim = predicted_pos -- confident: full lead
elseif c.overall < 0.55 then
aim = current_pos -- uncertain: aim where they are
endprediction.reset_confidence(target) ā
Resets one target's motion history (keeps the velocity tracker). Useful after displacing a target (e.g. a hook reel).
prediction.reset() ā
Clears all tracking state for every target.
Example ā
function on_tick()
local me = local_player()
if not me then return end
local speed = me:get_active_projectile_speed()
if speed <= 0 then return end
local target = targeting.find_closest_by_distance(60)
if not target or not target:is_alive() then return end
local origin = me:get_head_world()
local aim, flight = prediction.solve_linear(target, origin, speed, "head", 0.06)
if not aim then return end
local c = prediction.get_confidence(target)
local final
if c.overall >= 0.90 then
final = aim
elseif c.overall < 0.55 then
final = target:bone_pos("head") or target:get_position()
else
return -- hysteresis gap: hold
end
-- convert final (world vec3) to a view angle and apply
endFor ability projectiles (e.g. Haze sleep dagger):
local dagger = me:get_ability(slot.ability1)
if dagger and dagger.projectile_speed > 0 then
local aim, t = prediction.solve_linear(target, dagger.projectile_speed, "chest")
endFunction Index ā
| Function | Returns |
|---|---|
prediction.solve_linear(target, [source_pos,] speed, [bone], [extra]) | vec3, number | nil, nil |
prediction.solve_ballistic(target, [source_pos,] speed, gravity, up_speed, [bone], [extra]) | vec3, number | nil, nil |
prediction.predict(target, time, [bone]) | vec3 |
prediction.get_velocity(target) | vec3 vel, vec3 accel |
prediction.get_state(target) | table |
prediction.get_confidence(target, [ramp_seconds]) | table |
prediction.reset_confidence(target) | - |
prediction.reset() | - |