The package keeps the rules and hands them over each step: speed and heading, whether a jump leaves, CHAR_STEP and the boots' slope limit. The walker (Jolt CharacterVirtual) does gravity, landing, steps, slopes and following the ground down. Too steep is ground too steep facing the way the body wanted to go; stepping off an edge is not. ludic.physics: phys_walker_move (follow the body if something else moved it, then step), the slope limit, the jump always honoured. The character's tests run on a real Jolt walker over the same fake world: 15 pass, a knee-high boulder now a step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
41 lines
1.7 KiB
Text
41 lines
1.7 KiB
Text
# ground.ludic - what the feet stand on, and the one rule for a step versus a slope
|
|
# The ground at (x, z) for feet at from_y, a collider's top included when it is a step up. Only
|
|
# the body's own movement asks this: the ground itself (CharacterGround.height) stays pure, or
|
|
# the height ratchets on itself and the body ends up on things it never climbed.
|
|
export function char_stand_at(x: float, z: float, from_y: float) -> float {
|
|
let base = CharacterGround.height(x, z)
|
|
return CharacterGround.top_at(x, z, 0.35, Math.max(from_y, base), CHAR_STEP, base)
|
|
}
|
|
|
|
# a body of radius r at (x, z) from y0 to y1, out of every collider: the resolved point is chr_out_*
|
|
function chr_push(character_st: mut CharacterState, x: float, z: float, r: float, y0: float, y1: float) -> bool {
|
|
character_st.chr_ask_x = x
|
|
character_st.chr_ask_z = z
|
|
let hit = CharacterGround.push(x, z, r, y0, y1)
|
|
character_st.chr_out_x = x
|
|
character_st.chr_out_z = z
|
|
if hit {
|
|
character_st.chr_out_x = CharacterGround.pushed_x()
|
|
character_st.chr_out_z = CharacterGround.pushed_z()
|
|
}
|
|
return hit
|
|
}
|
|
|
|
function chr_bed_depth(x: float, z: float) -> float { return CharacterGround.water(x, z) - CharacterGround.bed(x, z) }
|
|
|
|
export function char_wrap(a: float) -> float {
|
|
var d = a
|
|
while d > chr_pi { d = d - 2.0 * chr_pi }
|
|
while d < -chr_pi { d = d + 2.0 * chr_pi }
|
|
return d
|
|
}
|
|
|
|
export function char_toward(cur: float, target: float, rate: float) -> float {
|
|
return cur + Math.clamp(target - cur, -rate, rate)
|
|
}
|
|
|
|
# did the gait's phase step over `mark` this frame? (it wraps at 2 pi)
|
|
function chr_cross(a: float, b: float, mark: float) -> bool {
|
|
if b < a { return a > mark or b < mark }
|
|
return a < mark and not (b < mark)
|
|
}
|