Steve's Roc Apps roc-apps on GitHub

Safari Ride Chapters

Chapter 28

The throttle

The rider is always choosing whether to accelerate, brake or hold steady. The Throttle module returns that choice once a step, as an acceleration.

Throttle EncounterReaction  asks about the encounter Gawking  asks about a pig Coast  coasts once ThrottleLimit  folds limits VehicleLimits  starts from the cruise acceleration RiderMind  asks for an acceleration Speed Acceleration  makes one Throttle EncounterReaction  asks about the encounter Gawking  asks about a pig Coast  coasts once ThrottleLimit  folds limits VehicleLimits  starts from the cruise acceleration RiderMind  asks for an acceleration Speed Acceleration  makes one
The modules around Throttle
offered bend_limit encounter_throttle gawk_brake shoulder_limit acceleration offered bend_limit encounter_throttle gawk_brake shoulder_limit acceleration
The chain of limits

The rider has several reasons to slow down, and each is a rule of its own. A bend is coming. A cat is in the lane. There's a pig to look at. Coasting on would carry the bike off the road. Throttle writes them as a list of limits, the chain in the second diagram. It starts from offered, the cruise acceleration. fold hands each limit in turn to held_to, with the acceleration so far, and each limit lets it through or lowers it:

Throttle.roc, in next_acceleration
# ...
limits = [
	bend_limit(motorcycle, segment),
	EncounterReaction.encounter_throttle(segment.encounter, encounter_stage),
	Gawking.gawk_brake(motorcycle, segment),
	shoulder_limit(motorcycle),
]
limits.fold(offered, held_to)
# ...

Call this the chain of limits. Each limit reads alone, and a new reason to slow down is one more line in the list.

Each limit is a ThrottleLimit: NoLimit, or AtMost some acceleration. held_to takes the lower:

ThrottleLimit.roc
## Nothing, or to accelerate no harder than this.
ThrottleLimit := [NoLimit, AtMost(Acceleration)].{
	is_eq : _
}
Throttle.roc
## The acceleration `a`, held to what `limit` asks.
held_to : Acceleration, ThrottleLimit -> Acceleration
held_to = |a, limit|
	match limit {
		AtMost(most) => a.min(most)
		NoLimit => a
	}

The throttle returns an acceleration, and the motorcycle's step turns it into a speed.

Speed and Acceleration are a pair like the clock and its steps: a speed is a value, and an acceleration is the change from one speed to another. To brake for a bend, the rider needs the steady acceleration that takes its speed to a target within so much road. That's Speed.acceleration_to. Its within is a Distance: the road the change has to happen in. The formula is the first line of a physics textbook, v² = u² + 2ad, solved for a, with the speed going from s to t over the distance within. In the parameter list, Speed.(s) and target: Speed.(t) unwrap the two speeds into plain numbers, as Clock.(n) does in the “Opaque types: the clock” chapter:

Speed.roc
## The steady acceleration that brings this speed to `target` within this
## distance.
acceleration_to : Speed, { target : Speed, within : Distance } -> Acceleration
acceleration_to = |Speed.(s), { target: Speed.(t), within }|
	Acceleration.metres_a_step_each_step((t * t - s * s) / (2.0 * within.to_metres()))

It has one caller, VehicleLimits.braking_to. That function also holds the speed steady when at most a micrometre of road is left, and enforces the invariant that every speed is zero or more, with a crash. The rider's bend brake, the gawk brake and the truck all brake through braking_to. The first limit is the bend's:

Throttle.roc
## Near the end of the segment, the brake that brings the rider to the exit
## speed at the point where they commit to the turn. Past that point there is
## no road left to brake on, and the limit holds the acceleration at none.
bend_limit : Motorcycle, Segment -> ThrottleLimit
bend_limit = |motorcycle, segment| {
	rider_along = motorcycle.route_place.road_point.along
	if is_near_end(rider_along, segment.length) {
		segment_exit_speed = exit_speed(segment)
		room_to_commit = segment.commit_along().distance_from(rider_along)
		AtMost(
			VehicleLimits.braking_to(
				motorcycle.speed,
				{ target: segment_exit_speed, within: room_to_commit },
			),
		)
	} else {
		NoLimit
	}
}

exit_speed is the speed the bend ahead allows, or rest on the route's last segment, so the same limit brings the rider to a stop at the end of the road.

The last limit, shoulder_limit, comes back in the “Variables and loops: rehearsal” chapter, because it runs a pretend copy of the bike forward, which is what that chapter is about. The two in the middle come from what the rider sees: encounter_throttle is the cat's, and gawk_brake the pig's. The cat's comes in the “What a cat does to the rider” chapter, and the pig's comes after Rehearsal, in the “The rider's gaze” chapter.