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>
125 lines
6.3 KiB
Text
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 { }
|