Chapter 27
The rider
Where the rider is depends on where it was a step ago, so the ride carries the rider in the moment and moves it one step at a time. The rider is the larger of the two stepped things, and these modules do its stepping, and the truck's:
A box is a module, and an arrow points to a module it calls. This chapter explains the rider and its motorcycle. The throttle, the lean search and the gaze each get a chapter of their own after it, and the truck's chapters follow.
A rider is two parts: a motorcycle, and a pair of eyes.
# ...
Rider := { motorcycle : Motorcycle, eyes : RiderEyes }.{
# ...
Both fields are nominal records of their own, and between them they hold six values:
| Part | Field | Type | What it is |
|---|---|---|---|
| motorcycle | route_ |
RoutePlace |
which segment, and where on it, along and across |
yaw |
Angle. |
which way the bike points | |
speed |
Speed |
how far it goes in a step | |
lean |
Angle. |
how far it leans | |
| eyes | gaze_ |
Angle. |
where the rider is looking |
look_ |
Fraction |
how deep the last look went |
Three of the six are angles, of two types: a Lean is the bike's tilt, apart from a Turn, so passing one where the other belongs fails to compile, as a bearing and a turn did in the “Mountains” chapter.
The rider's step §
The step works in order, and the code reads that way: decide, move, cross a bend or stop, then choose where to look:
## The rider a step on along `route`, with the encounter on their segment at
## `encounter_stage`: the move, the crossing onto the next segment, the stop
## at the route's end, and then where the rider looks.
rider_at_next_step : Rider, { route : Route, encounter_stage : Animation.Stage } -> Rider
rider_at_next_step = |rider, { route, encounter_stage }| {
segment = rider.motorcycle.route_place.segment_on(route)
moved = rider.motorcycle.motorcycle_at_next_step(
RiderMind.controls(rider.motorcycle, { segment, encounter_stage }),
)
motorcycle =
match segment.segment_ending() {
Bends({ bend, .. }) => moved.cross_bend_if_on_road(bend)
RouteEnds => stop_at_route_end(moved, segment)
}
gaze_target = RiderMind.gaze_target(motorcycle, motorcycle.route_place.segment_on(route))
{ motorcycle, eyes: rider.eyes.eyes_at_next_step(gaze_target) }
}
The match on the segment's ending has two tags. Bends({ bend, .. }) takes the payload's bend field. In a pattern, .. stands for the rest of a record's fields.
Deciding §
Every decision the rider makes belongs to RiderMind:
## The lean toward the best line, and the acceleration the throttle gives
## the motorcycle once it leans so, on `segment`, the one it is on, with the
## segment's encounter at `encounter_stage`.
controls : Motorcycle,
{
segment : Segment,
encounter_stage : Animation.Stage,
} -> Motorcycle.Controls
controls = |motorcycle, situation| {
lean = LeanSearch.best_lean(motorcycle)
{ lean, acceleration: Throttle.next_acceleration(motorcycle.with_lean(lean), situation) }
}
A decision is the bike's controls, a lean and an acceleration:
## What the rider sets for a step: the lean the motorcycle takes, and how
## hard it accelerates.
Controls : { lean : Angle.Lean, acceleration : Acceleration }
Look at the last line of controls. controls passes Throttle.next_acceleration a pretend motorcycle: this motorcycle as it would be at the new lean. Two small helpers serve such pretend motorcycles:
with_lean : Motorcycle, Angle.Lean -> Motorcycle
with_lean = |motorcycle, lean| { ..motorcycle, lean }
## The controls that keep this motorcycle's lean and speed as they are.
steady_controls : Motorcycle -> Motorcycle.Controls
steady_controls = |motorcycle| { lean: motorcycle.lean, acceleration: Acceleration.steady }
{ ..motorcycle, lean } is a record update with a shorthand field: the field and the argument share the name lean. with_lean makes a motorcycle as it would be at another lean, and the real motorcycle stays as it was. steady_controls makes the controls that keep the lean and the speed as they are. The rider's steering, in the “Variables and loops: rehearsal” chapter, uses both.
One step of physics §
A lean of one degree turns the bike by a tenth of a degree each step, so at a metre a step it goes round a circle with a radius of 573 metres. That's nearly all the physics. Here's the motorcycle:
# ...
Motorcycle := {
route_place : RoutePlace,
yaw : Angle.Turn,
speed : Speed,
lean : Angle.Lean,
}.{
# ...
The step is the whole of the physics, in order:
## The motorcycle one step on, at the lean its controls set and the speed
## their acceleration gives, held to the top speed.
motorcycle_at_next_step : Motorcycle, Motorcycle.Controls -> Motorcycle
motorcycle_at_next_step = |motorcycle, { lean, acceleration }| {
speed = allowed_speed(motorcycle.speed.accelerated_by(acceleration))
step_length = speed.step_length()
turn = lean.steer(turn_per_lean)
mid = motorcycle.yaw.plus(turn.times(0.5)).to_radians()
place = motorcycle.route_place
{
route_place: {
..place,
road_point: {
along: place.road_point.along.plus(step_length.times(mid.cos())),
across: place.road_point.across.plus(step_length.times(mid.sin())),
},
},
yaw: motorcycle.yaw.plus(turn),
speed,
lean,
}
}
It works in three parts:
- The speed.
accelerated_byadds the acceleration to the speed, andallowed_speedholds the result to the top speed. - The turn.
lean.steer(turn_per_lean)turns the lean into a change of direction, aTurn, and that's the only way a lean changes where the bike points. - The move. The bike goes one step's length in the direction halfway through the step's turn,
mid.along.plus(…)adds aDistanceto a place along the road, asClock.plusadds a length to a moment. The new place is a record update ofroute_place, inside a new motorcycle.
The bike turns the same amount per step at any speed. That's a cheat, chosen by eye, and it holds up because the rider chooses each step's lean by trying it out on this same physics.
The end of a segment §
The end of a segment uses a small version of the same trying out. cross_bend_if_on_road makes a motorcycle carried past the bend onto the next segment. If that motorcycle is on the road, it's the answer, and if it's off the road, the old one is:
## This motorcycle carried over `bend`, the bend out of its segment, when
## that puts it on the next segment's road: placed on that segment and turned
## to its axis. Short of that, it stays where it is.
cross_bend_if_on_road : Motorcycle, Geom.Bend -> Motorcycle
cross_bend_if_on_road = |motorcycle, bend| {
on_next = motorcycle.route_place.route_place_past_bend(bend)
if on_next.is_on_road() {
{
..motorcycle,
route_place: on_next,
yaw: motorcycle.yaw.minus(bend.turn),
}
} else {
motorcycle
}
}
At the end of the road, the throttle brakes the bike to rest. A last step can still carry it past the end, so stop_at_route_end holds it there with Motorcycle.stopped_at, from the “Records” chapter.
The picture leans too §
When the bike leans, the camera rolls with it, and the whole picture leans:
## How far the canvas rolls with the rider's `lean`: not at all inside the
## deadband, and with the lean past it.
rider_roll : Angle.Lean -> Angle.Lean
rider_roll = |lean|
if lean.abs() < roll_deadband {
Angle.upright
} else {
lean
}
A lean inside roll_deadband, a thousandth of a radian, leaves the picture level, and any other lean passes straight through. The camera turns as well: the rider's head follows the gaze and turns a little into a bend, and the camera adds the head's turn to the motorcycle's yaw, as the “The rider's gaze” chapter shows.
So for each step, RiderMind.controls returns a lean and an acceleration, and motorcycle_at_next_step turns them into a new place on the road. The next chapter shows how the throttle chooses the acceleration. The “Variables and loops: rehearsal” chapter shows how the rider chooses the lean.