ludic/packages/ludic.character/steer.ludic
Orkuncakilkaya 71735b10a2 feat(base): 0.R4 - a queue keeps its own count, so every package verb takes only its own state; ludic migrate state --prune
ludic.base's Queue<T> carries a QueueTag (its name and pending count): queue_new(name), q_push(q, v),
q_drain(q), q_clear(q) take no BaseState, and core_undrained(tags) names the given queues still holding
facts. A reducer on a package's state can now call that package's verbs (wallet_earn(wallet_st, n)).

ludic migrate state --prune (ludicc --migrate-prune) takes out each state parameter a function no
longer uses, nor anything it calls, and the argument that fills it - including an argument for a
parameter the callee has dropped, which is taken out before the call is checked, so a generic's T is
told by the argument that says it. A reducer keeps its state. --dry-run now counts the edits it would
make. Every package was moved with it: 608 base_st parameters and their arguments, 1889 edits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 04:19:56 +03:00

61 lines
3.6 KiB
Text

# steer.ludic - turning to the stick and easing to the speed it asks; then the gait and the orbit
function chr_turn_to(character_st: mut CharacterState, dx: float, dz: float, rate: float) -> float {
let diff = char_wrap(Math.atan2(-dx, -dz) - character_st.chr_yaw)
character_st.chr_yaw = character_st.chr_yaw + Math.clamp(diff, -rate, rate)
return diff
}
function chr_steer(character_st: mut CharacterState, dx: float, dz: float, mag: float, run: bool, dt: float) -> void {
var target = 0.0
if mag > 0.05 and not character_st.chr_air {
let diff = chr_turn_to(character_st, dx, dz, Math.lerp(10.0, 5.0, character_st.chr_run) * dt)
let lined = Math.clamp((Math.cos(diff) + 0.3) / 1.3, 0.0, 1.0)
var top = character_st.chr_walk_speed * CharacterBody.pace()
if run { top = character_st.chr_run_speed * CharacterBody.pace() }
if CharacterBody.sneaking() { top = 1.3 }
target = mag * top * lined
} else if CharacterBody.holding() and not character_st.chr_air {
# a hold turns the body to the thing being worked, so the work is seen being done
chr_turn_to(character_st, CharacterBody.hold_x() - character_st.chr_x, CharacterBody.hold_z() - character_st.chr_z, 6.0 * dt)
}
# in the water the top speed and the inertia are the water's: a swim is half a walk
if character_st.chr_swim {
target = mag * 1.5
if run { target = mag * 2.4 }
if mag > 0.05 { chr_turn_to(character_st, dx, dz, 4.0 * dt) }
}
if not character_st.chr_air {
var acc = 12.0
if target > character_st.chr_speed { acc = 9.0 }
if character_st.chr_swim { acc = 4.5 }
character_st.chr_speed = char_toward(character_st.chr_speed, target, acc * dt)
}
}
# The gait's phase is distance over the stride, so the feet never slide; a boot comes down as the
# phase crosses 0 and again as it crosses pi, and that is when a footfall is said.
function chr_gait(character_st: mut CharacterState, dist: float, dyaw: float, dpitch: float, dt: float) -> void {
if character_st.chr_land > 0.0 { character_st.chr_land = character_st.chr_land - dt }
if character_st.chr_steep > 0.0 { character_st.chr_steep = character_st.chr_steep - dt }
if character_st.chr_swim { character_st.chr_swim_phase = (character_st.chr_swim_phase + dt * (1.6 + character_st.chr_speed * 0.9)) % (2.0 * chr_pi) }
let stride = Math.lerp(1.5, 2.7, character_st.chr_run)
let phase0 = character_st.chr_phase
character_st.chr_phase = (character_st.chr_phase + dist / stride * (2.0 * chr_pi)) % (2.0 * chr_pi)
if not character_st.chr_air and not character_st.chr_swim and not character_st.chr_sit and character_st.chr_speed > 0.4 {
if chr_cross(phase0, character_st.chr_phase, 0.0) or chr_cross(phase0, character_st.chr_phase, chr_pi) { chr_say(character_st, CHAR_FOOTFALL, character_st.chr_speed) }
}
var mv = 0.0
if character_st.chr_speed > 0.12 { mv = 1.0 }
character_st.chr_move = char_toward(character_st.chr_move, mv, 5.0 * dt)
character_st.chr_run = char_toward(character_st.chr_run, Math.clamp((character_st.chr_speed - 3.6) / 2.8, 0.0, 1.0), 3.0 * dt)
chr_look(character_st, dyaw, dpitch)
# the orbit settles slowly behind a walking body unless the player is holding the look
if dyaw == 0.0 and character_st.chr_speed > 0.4 and not character_st.chr_lock and not CharacterInput.look_always() {
character_st.chr_cam_yaw = character_st.chr_cam_yaw + char_wrap(character_st.chr_yaw - character_st.chr_cam_yaw) * Math.min(1.0, 1.1 * dt)
}
}
function chr_look(character_st: mut CharacterState, dyaw: float, dpitch: float) -> void {
character_st.chr_cam_yaw = character_st.chr_cam_yaw + dyaw
character_st.chr_cam_pitch = Math.clamp(character_st.chr_cam_pitch + dpitch, -0.96, 0.61)
}