ludic/packages/ludic.character/ports.ludic
Orkuncakilkaya e909ec122c feat(character): 16.8 - on land a physics walker moves the body (CharacterGround.walk); the package's own gravity, ballistic air and slope-probe refusal are gone
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>
2026-09-27 00:47:14 +03:00

125 lines
6.3 KiB
Text

# ports.ludic - what the body asks the game. Unbound, the world is a dry plain with no colliders,
# the boots manage 1.05 m a metre, the body is fresh and nothing holds or carries it.
export port CharacterGround {
height: fn(float, float) -> float # the world's own ground: pure, never the body's
# (x, z, r, from_y, step, base): the top of a collider within `step` above from_y, else base
top_at: fn(float, float, float, float, float, float) -> float = fn chr__top_plain
bed: fn(float, float) -> float = fn chr__bed_plain # the bed under water (unbound: the ground)
water: fn(float, float) -> float = fn chr__dry # the surface over a point
# (x, z, r, y0, y1): true when a collider takes a body of radius r there; pushed_x / _z say where to
push: fn(float, float, float, float, float) -> bool = fn chr__no_push
pushed_x: fn() -> float = fn chr__asked_x
pushed_z: fn() -> float = fn chr__asked_z
slope: fn(float, float) -> float = fn chr__slope_plain # 0 flat .. 1 a wall
slope_limit: fn() -> float = fn chr__boots # the rise a metre the boots take
current: fn(float, float, float, float) -> float = fn chr__still # (x, z, fx, fz): 0..1 of a stroke lost
# (x, y, z, vx, vz, jump, step, max_slope_deg, dt): the body on land moved from where it stands at
# (vx, vz) across, jump > 0 leaving the ground at that speed up; the ground after it (CHAR_WALK_*),
# and walked_x / _y / _z where it is. The game's physics walker; there is no default mover.
walk: fn(float, float, float, float, float, float, float, float, float) -> int
walked_x: fn() -> float
walked_y: fn() -> float
walked_z: fn() -> float
}
export port CharacterBody {
pace: fn() -> float = fn chr__one # the walk and run, times (gear, a tired body)
sneaking: fn() -> bool = fn chr__no
breath: fn() -> float = fn chr__one # the lungs, times
riding: fn() -> bool = fn chr__no # something else carries the body
ride: fn(float, float, bool, float) -> void = fn chr__not_carried # (ix, iz, run, dt) while riding
holding: fn() -> bool = fn chr__no # at work on something: face it
hold_x: fn() -> float = fn chr__zero
hold_z: fn() -> float = fn chr__zero
eyes_ok: fn() -> bool = fn chr__yes # first person may be used now
}
# the stick, once a frame: move is in the camera's frame (+z ahead), look is radians this frame
export port CharacterInput {
move_x: fn() -> float = fn chr__zero
move_z: fn() -> float = fn chr__zero
run: fn() -> bool = fn chr__no
jump: fn() -> bool = fn chr__no
look_yaw: fn() -> float = fn chr__zero
look_pitch: fn() -> float = fn chr__zero
zoom: fn() -> float = fn chr__zero # wheel notches, in toward the body
look_always: fn() -> bool = fn chr__no # the look is held: never settle behind
}
export state CharacterState {
chr_out_x: float = 0.0
chr_out_z: float = 0.0
chr_ask_x: float = 0.0
chr_ask_z: float = 0.0
chr_x: float = 0.0
chr_y: float = 0.0
chr_z: float = 0.0
chr_yaw: float = 0.0 # facing, radians; 0 faces -z
chr_speed: float = 0.0 # m/s along the facing
chr_phase: float = 0.0 # the gait: one cycle is two steps, advanced by ground covered
chr_move: float = 0.0 # 0 standing .. 1 moving
chr_run: float = 0.0 # 0 walking .. 1 running
chr_time: float = 0.0
chr_water: float = -10000.0 # the water line under the body, every update
chr_walk_speed: float = 3.2
chr_run_speed: float = 6.8
chr_vy: float = 0.0
chr_air: bool = false
chr_air_vx: float = 0.0
chr_air_vz: float = 0.0
chr_land: float = 0.0 # seconds of the landing's crouch left
chr_steep: float = 0.0 # seconds of "that was too steep" left to say
chr_can_run: bool = true
chr_step: float = 0.0 # metres covered this frame
chr_sit: bool = false
chr_sit_mode: int = 0
chr_sit_t: float = 0.0 # 0 standing .. 1 sat, eased over a quarter second
chr_sit_x: float = 0.0 # the seat's point, walked to over the blend
chr_sit_z: float = 0.0
chr_sit_yaw: float = 0.0
chr_swim: bool = false
chr_dive: bool = false
chr_depth: float = 0.0 # metres below the surface
chr_breath: float = 0.0
chr_float: float = 0.0 # 0 swimming .. 1 floating
chr_swim_phase: float = 0.0
chr_edge_said: float = 0.0
chr_holds: int = CHAR_HOLD_SCREEN
chr_cam_yaw: float = 0.0
chr_cam_pitch: float = -0.157
chr_cam_dist: float = 4.2
chr_lock: bool = false # the orbit never settles behind on its own
chr_fps: bool = false
chr_fps_bob: float = 0.0
chr_piv_x: float = 0.0
chr_piv_y: float = 0.0
chr_piv_z: float = 0.0
chr_eye_x: float = 0.0 # where the camera is and where it looks, once computed
chr_eye_y: float = 0.0
chr_eye_z: float = 0.0
chr_eye_yaw: float = 0.0
chr_eye_pitch: float = 0.0
chr_eyes_now: bool = false # the last camera was the eyes: the figure is hidden
chr_safe_x: float = 0.0
chr_safe_z: float = 0.0
chr_fact_q: Queue<CharacterFact> = null
}
function chr__top_plain(x: float, z: float, r: float, from_y: float, step: float, base: float) -> float { return base }
function chr__bed_plain(x: float, z: float) -> float { return CharacterGround.height(x, z) }
function chr__dry(x: float, z: float) -> float { return -10000.0 }
function chr__no_push(x: float, z: float, r: float, y0: float, y1: float) -> bool { return false }
function chr__asked_x(character_st: CharacterState) -> float { return character_st.chr_ask_x }
function chr__asked_z(character_st: CharacterState) -> float { return character_st.chr_ask_z }
function chr__slope_plain(x: float, z: float) -> float {
let e = 2.0
let dx = CharacterGround.height(x + e, z) - CharacterGround.height(x - e, z)
let dz = CharacterGround.height(x, z + e) - CharacterGround.height(x, z - e)
return 1.0 - 4.0 / Math.sqrt(dx * dx + dz * dz + 16.0)
}
function chr__boots() -> float { return 1.05 }
function chr__still(x: float, z: float, fx: float, fz: float) -> float { return 0.0 }
function chr__one() -> float { return 1.0 }
function chr__zero() -> float { return 0.0 }
function chr__no() -> bool { return false }
function chr__yes() -> bool { return true }
function chr__not_carried(ix: float, iz: float, run: bool, dt: float) -> void { }