vehicles: a kind's numbers are a VehicleSpec the game hands over (vehicles_spec), not constants

The seat height, the horse's walk, gallop, acceleration and turn (VE_HORSE_*), its stamina (a gallop
over 6 m/s spends 6 a second, a walk gives back 3, 5 needed to gallop) and hay (0.02 a metre,
VEHICLE_HAY_MIN), the boat's stroke, turn and wind (VE_ROW, VE_ROW_TURN, VE_WIND) and where a rider
gets off (the horse's 1.2 m flank, the boat's 1.25 m wade) move into VehicleSpec (spec.ludic), held
per kind in VehiclesState.ve_specs and kept across a reset. vehicles_spec(kind, spec) sets one,
vehicle_spec(kind) asks it; a kind with no spec cannot be called. VEHICLE_HORSE and VEHICLE_BOAT stay
the kinds. The tests hand Maroon Lake's numbers in, and a new one holds a spec's walk and seat_h.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 20:17:31 +03:00
parent a87f20c66e
commit 668a9ecf69
10 changed files with 137 additions and 34 deletions

8
changes/vehicles-spec.md Normal file
View file

@ -0,0 +1,8 @@
bump: minor
type: feature
**`ludic.vehicles`: a kind's numbers are the game's.** The seat height, a horse's walk, gallop,
acceleration and turn, its stamina and hay, a boat's stroke, turn and the wind on it, and where a rider
gets off (the horse's flank, the boat's wade) were constants in the package; they are a `VehicleSpec` a
game hands each kind with `vehicles_spec(kind, spec)` before any vehicle of it is called, and
`vehicle_spec(kind)` asks them. A kind with no spec cannot be called, and a reset keeps the specs.
`VEHICLE_HAY_MIN` is gone: it is the horse spec's `hay_min`.

View file

@ -16,12 +16,17 @@ through the game).
## The rules it keeps ## The rules it keeps
- **A kind's numbers are the game's data.** The package keeps the rules (a walk and a gallop on
stamina, a stroke and a turn, where a rider gets off) and no numbers: the game hands each kind a
`VehicleSpec` with `vehicles_spec(kind, spec)` - in Maroon Lake from its `VehicleKinds` table -
once, before any vehicle is made. A kind with no spec cannot be called; a reset keeps the specs.
- **Nothing is parked for anyone.** A vehicle has an `owner` and a `rider`; `vehicle_find(kind)` is - **Nothing is parked for anyone.** A vehicle has an `owner` and a `rider`; `vehicle_find(kind)` is
this player's own and nothing else may pick "the" boat. A place is taken while a boat lies in it this player's own and nothing else may pick "the" boat. A place is taken while a boat lies in it
or a horse is tied at it (`home_x`, `home_z`), within half a place's spacing. or a horse is tied at it (`home_x`, `home_z`), within half a place's spacing.
- **Where a body is put down is one question with one answer.** Getting off asks the rider's - **Where a body is put down is one question with one answer.** Getting off asks the rider's
`safe_spot` - a wade of up to 1.25 m within its search from a boat, beside the right flank from a `safe_spot` - a wade of up to the spec's `off_depth` within its search from a boat, `off_side` out
horse - and a boat too far out refuses and says so (`VEHICLE_NO_LANDING`) rather than dropping the by the right flank from a horse - and a boat too far out refuses and says so (`VEHICLE_NO_LANDING`) rather than dropping the
rider in the lake. rider in the lake.
- **A boat is a body in the game's physics** (`VehicleHull`, phase 16; a buoyant Jolt hull in Maroon - **A boat is a body in the game's physics** (`VehicleHull`, phase 16; a buoyant Jolt hull in Maroon
Lake). The package rows it - the stick forward and back is a stroke along the bow, left and right Lake). The package rows it - the stick forward and back is a stroke along the bow, left and right
@ -66,7 +71,9 @@ export port VehicleRider {
| | | | | |
| --- | --- | | --- | --- |
| `VEHICLE_HORSE`, `VEHICLE_BOAT`, `VEHICLE_PLACES`, `VEHICLE_HAY_MIN` | the kinds, four places each, and the hay a horse needs to go | | `VEHICLE_HORSE`, `VEHICLE_BOAT`, `VEHICLE_PLACES`, `VEHICLE_KINDS` | the kinds, four places each, and how many kinds may have a spec |
| `VehicleSpec { seat_h, walk, run, acc, turn, row, row_turn, wind, hay_per_m, hay_min, run_min, tire_at, tire, rest, off_side, off_depth }` | a kind's numbers: the seat, a horse's pace, stamina and hay, a boat's oars and wind, where one gets off |
| `vehicles_spec(kind, spec)`, `vehicle_spec(kind)` | set a kind's numbers (at boot), and ask them |
| `Vehicle { kind, nid, x, y, z, yaw, speed, fuel, food, seat_h, owned, owner, rider, home_x, home_z }` | a vehicle (change one through the verbs) | | `Vehicle { kind, nid, x, y, z, yaw, speed, fuel, food, seat_h, owned, owner, rider, home_x, home_z }` | a vehicle (change one through the verbs) |
| `vehicles_dock_at(x, z, yaw)`, `vehicles_rail_at(x, z, yaw)`, `vehicles_dock_yaw()` | where the places are measured from | | `vehicles_dock_at(x, z, yaw)`, `vehicles_rail_at(x, z, yaw)`, `vehicles_dock_yaw()` | where the places are measured from |
| `vehicle_berth_x/z(k)`, `vehicle_post_x/z(k)`, `vehicle_bay_x/z(k)`, `vehicle_place_x/z(kind, k)` | the places | | `vehicle_berth_x/z(k)`, `vehicle_post_x/z(k)`, `vehicle_bay_x/z(k)`, `vehicle_place_x/z(kind, k)` | the places |
@ -85,7 +92,8 @@ export port VehicleRider {
ludic test packages/ludic.vehicles ludic test packages/ludic.vehicles
``` ```
A fake shore with a pier head and a trough: berths and bays, calling and fetching, a taken place, a A fake shore with a pier head and a trough, and Maroon Lake's numbers as the specs: a kind with no
spec refused and a spec's walk and seat obeyed, berths and bays, calling and fetching, a taken place, a
player leaving, a hungry horse, riding a horse into a trunk and to the water's edge; a real Jolt hull player leaving, a hungry horse, riding a horse into a trunk and to the water's edge; a real Jolt hull
rowed out, losing its stroke to the swell, turned, stopped by the shore's bed and drifted by the rowed out, losing its stroke to the swell, turned, stopped by the shore's bed and drifted by the
wind; getting off near a shore and far from one, and the save. wind; getting off near a shore and far from one, and the save.

View file

@ -3,17 +3,17 @@
@alloc_ok("a player's own boat or horse, made the first time it is called and fetched after") @alloc_ok("a player's own boat or horse, made the first time it is called and fetched after")
function ve_add(vehicles_st: VehiclesState, kind: int, owner: int, x: float, z: float, yaw: float) -> Vehicle { function ve_add(vehicles_st: VehiclesState, kind: int, owner: int, x: float, z: float, yaw: float) -> Vehicle {
if kind != VEHICLE_HORSE and kind != VEHICLE_BOAT { return null } if kind != VEHICLE_HORSE and kind != VEHICLE_BOAT { return null }
let spec = vehicle_spec(vehicles_st, kind)
if spec == null { return null }
let v = new Vehicle let v = new Vehicle
v.nid = VehicleWorld.next_id() v.nid = VehicleWorld.next_id()
v.kind = kind v.kind = kind
v.owner = owner v.owner = owner
v.yaw = yaw v.yaw = yaw
v.seat_h = spec.seat_h
if kind == VEHICLE_HORSE { if kind == VEHICLE_HORSE {
v.seat_h = 1.35
v.fuel = 100.0 v.fuel = 100.0
v.food = 40.0 v.food = 40.0
} else {
v.seat_h = 0.30
} }
ve_put(v, x, z) ve_put(v, x, z)
ve_file(vehicles_st.ve, v) ve_file(vehicles_st.ve, v)

View file

@ -1,19 +1,16 @@
# horse.ludic - a horse under the stick: a walk, a canter on stamina, hay for every metre; it will # horse.ludic - a horse under the stick: a walk, a canter on stamina, hay for every metre; it will
# not step into water, and its legs (VehicleLegs) meet the ground and whatever stands in its way # not step into water, and its legs (VehicleLegs) meet the ground and whatever stands in its way
const VE_HORSE_WALK: float = 4.5 # (its numbers are its kind's VehicleSpec: walk, run, acc, turn, the stamina and the hay)
const VE_HORSE_GALLOP: float = 11.0
const VE_HORSE_ACC: float = 4.0
const VE_HORSE_TURN: float = 1.6
function ve_ride_horse(vehicles_st: mut VehiclesState, v: Vehicle, ix: float, iz: float, run: bool, dt: float) -> void { function ve_ride_horse(vehicles_st: mut VehiclesState, v: Vehicle, ix: float, iz: float, run: bool, dt: float) -> void {
var top = VE_HORSE_WALK let sp = vehicle_spec(vehicles_st, v.kind)
if run and v.fuel > 5.0 { top = VE_HORSE_GALLOP } var top = sp.walk
v.speed = v.speed + Math.clamp(iz * top - v.speed, -VE_HORSE_ACC * dt, VE_HORSE_ACC * dt) if run and v.fuel > sp.run_min { top = sp.run }
v.speed = v.speed + Math.clamp(iz * top - v.speed, -sp.acc * dt, sp.acc * dt)
# Steering follows SIGNED speed: backing up with the stick left swings the nose right # Steering follows SIGNED speed: backing up with the stick left swings the nose right
if Math.abs(v.speed) > 0.3 { if Math.abs(v.speed) > 0.3 {
var rate = Math.clamp(Math.abs(v.speed) / 3.0, 0.4, 1.0) var rate = Math.clamp(Math.abs(v.speed) / 3.0, 0.4, 1.0)
if v.speed < 0.0 { rate = -rate } if v.speed < 0.0 { rate = -rate }
v.yaw = v.yaw - ix * VE_HORSE_TURN * (dt * rate) v.yaw = v.yaw - ix * sp.turn * (dt * rate)
} }
let fx = -Math.sin(v.yaw) let fx = -Math.sin(v.yaw)
let fz = -Math.cos(v.yaw) let fz = -Math.cos(v.yaw)
@ -24,7 +21,7 @@ function ve_ride_horse(vehicles_st: mut VehiclesState, v: Vehicle, ix: float, iz
return return
} }
ve_legs_walk(vehicles_st, v, fx * v.speed, fz * v.speed, dt) ve_legs_walk(vehicles_st, v, fx * v.speed, fz * v.speed, dt)
ve_stamina(v, d, dt) ve_stamina(sp, v, d, dt)
} }
# the legs carry it from where it is; what they covered along its heading is its speed now, never # the legs carry it from where it is; what they covered along its heading is its speed now, never
@ -43,8 +40,8 @@ function ve_legs_walk(vehicles_st: mut VehiclesState, v: Vehicle, vx: float, vz:
} }
# a gallop spends stamina and a walk gives it back; every metre costs hay # a gallop spends stamina and a walk gives it back; every metre costs hay
function ve_stamina(v: Vehicle, d: float, dt: float) -> void { function ve_stamina(sp: VehicleSpec, v: Vehicle, d: float, dt: float) -> void {
if Math.abs(v.speed) > 6.0 { v.fuel = Math.max(v.fuel - 6.0 * dt, 0.0) } else { v.fuel = Math.min(v.fuel + 3.0 * dt, 100.0) } if Math.abs(v.speed) > sp.tire_at { v.fuel = Math.max(v.fuel - sp.tire * dt, 0.0) } else { v.fuel = Math.min(v.fuel + sp.rest * dt, 100.0) }
v.food = Math.max(v.food - Math.abs(d) * 0.02, 0.0) v.food = Math.max(v.food - Math.abs(d) * sp.hay_per_m, 0.0)
} }

View file

@ -5,6 +5,7 @@ module ludic_vehicles uses ludic_base
numbers float numbers float
import "ludic.base" import "ludic.base"
import "kinds.ludic" import "kinds.ludic"
import "spec.ludic"
import "table.ludic" import "table.ludic"
import "ports.ludic" import "ports.ludic"
import "fact_pool.ludic" import "fact_pool.ludic"

View file

@ -1,12 +1,10 @@
# mount.ludic - getting on, and getting off onto a spot the rider's own safe-spot rule found: the # mount.ludic - getting on, and getting off onto a spot the rider's own safe-spot rule found: the
# same one answer a seat and a stand-up use, so a landing is never inside a boulder or on a bank # same one answer a seat and a stand-up use, so a landing is never inside a boulder or on a bank
# the body cannot climb off # the body cannot climb off
export const VEHICLE_HAY_MIN: float = 10.0 # a horse with less hay than this will not go
# this machine's player onto v; false when it is somebody else's, taken, or a hungry horse # this machine's player onto v; false when it is somebody else's, taken, or a hungry horse
export function vehicle_mount(vehicles_st: mut VehiclesState, v: Vehicle) -> bool { export function vehicle_mount(vehicles_st: mut VehiclesState, v: Vehicle) -> bool {
if v == null or v.owner != 0 or v.rider >= 0 or vehicles_st.ve_cur != null { return false } if v == null or v.owner != 0 or v.rider >= 0 or vehicles_st.ve_cur != null { return false }
if v.kind == VEHICLE_HORSE and v.food < VEHICLE_HAY_MIN { if v.kind == VEHICLE_HORSE and v.food < vehicle_spec(vehicles_st, v.kind).hay_min {
ve_say(vehicles_st, VEHICLE_HUNGRY, v, v.food) ve_say(vehicles_st, VEHICLE_HUNGRY, v, v.food)
return false return false
} }
@ -17,21 +15,22 @@ export function vehicle_mount(vehicles_st: mut VehiclesState, v: Vehicle) -> boo
return true return true
} }
# Off, onto the nearest spot a body could stand and walk off: from a boat a wade of up to 1.25 m # Off, onto the nearest spot a body could stand and walk off: from a boat a wade of up to its spec's
# within ten metres, from a horse beside its right flank. False (and said) when a boat is too far out. # off_depth, from a horse off_side out by its right flank. False (and said) when a boat is too far out.
export function vehicle_dismount(vehicles_st: mut VehiclesState) -> bool { export function vehicle_dismount(vehicles_st: mut VehiclesState) -> bool {
let v = vehicles_st.ve_cur let v = vehicles_st.ve_cur
if v == null { return false } if v == null { return false }
let sp = vehicle_spec(vehicles_st, v.kind)
if v.kind == VEHICLE_BOAT { if v.kind == VEHICLE_BOAT {
if not VehicleRider.safe_spot(v.x, v.z, VehicleWorld.water(v.x, v.z), 1.25) { if not VehicleRider.safe_spot(v.x, v.z, VehicleWorld.water(v.x, v.z), sp.off_depth) {
ve_say(vehicles_st, VEHICLE_NO_LANDING, v, 0.0) ve_say(vehicles_st, VEHICLE_NO_LANDING, v, 0.0)
return false return false
} }
VehicleRider.step_off(VehicleRider.safe_x(), VehicleRider.safe_z()) VehicleRider.step_off(VehicleRider.safe_x(), VehicleRider.safe_z())
ve_moor(v) ve_moor(v)
} else { } else {
let wx = v.x + Math.cos(v.yaw) * 1.2 let wx = v.x + Math.cos(v.yaw) * sp.off_side
let wz = v.z - Math.sin(v.yaw) * 1.2 let wz = v.z - Math.sin(v.yaw) * sp.off_side
if VehicleRider.safe_spot(wx, wz, VehicleWorld.ground(v.x, v.z), 0.0) { if VehicleRider.safe_spot(wx, wz, VehicleWorld.ground(v.x, v.z), 0.0) {
VehicleRider.step_off(VehicleRider.safe_x(), VehicleRider.safe_z()) VehicleRider.step_off(VehicleRider.safe_x(), VehicleRider.safe_z())
} else { VehicleRider.step_off(v.x, v.z) } } else { VehicleRider.step_off(v.x, v.z) }

View file

@ -52,6 +52,7 @@ export state VehiclesState {
ve_sz: float = 0.0 ve_sz: float = 0.0
ve: VehicleTable = ve__new() ve: VehicleTable = ve__new()
ve_cur: Vehicle = null ve_cur: Vehicle = null
@max(8) ve_specs: []VehicleSpec = ve_specs_new() # each kind's numbers (vehicles_spec), kept across a reset
ve_dock_x: float = 0.0 ve_dock_x: float = 0.0
ve_dock_z: float = 0.0 ve_dock_z: float = 0.0
ve_dock_yaw: float = 0.0 ve_dock_yaw: float = 0.0

View file

@ -1,9 +1,6 @@
# ride.ludic - the rider's stick steers what they are on: a horse walks, canters on stamina and # ride.ludic - the rider's stick steers what they are on: a horse walks, canters on stamina and
# stops at water, a slope or a trunk; a boat is rowed - a stroke and a turn handed to its hull, # stops at water, a slope or a trunk; a boat is rowed - a stroke and a turn handed to its hull,
# which the water floats, the wind drifts and the lake bed stops # which the water floats, the wind drifts and the lake bed stops
const VE_ROW: float = 630.0 # N: a full stroke forward, about 3 m/s on Maroon Lake's hull
const VE_ROW_TURN: float = 270.0 # N.m: the oars pulled against each other, about 1.1 rad/s
const VE_WIND: float = 70.0 # N on a boat in a full wind
export function vehicle_update(vehicles_st: mut VehiclesState, ix: float, iz: float, run: bool, dt: float) -> void { export function vehicle_update(vehicles_st: mut VehiclesState, ix: float, iz: float, run: bool, dt: float) -> void {
let v = vehicles_st.ve_cur let v = vehicles_st.ve_cur
@ -18,7 +15,8 @@ export function vehicle_update(vehicles_st: mut VehiclesState, ix: float, iz: fl
function ve_row(vehicles_st: mut VehiclesState, v: Vehicle, ix: float, iz: float, dt: float) -> void { function ve_row(vehicles_st: mut VehiclesState, v: Vehicle, ix: float, iz: float, dt: float) -> void {
ve_hull_read(v) ve_hull_read(v)
let take = ve_swell(vehicles_st, v, iz, dt) let take = ve_swell(vehicles_st, v, iz, dt)
VehicleHull.row(v.nid, iz * VE_ROW * (1.0 - take), -ix * VE_ROW_TURN) let sp = vehicle_spec(vehicles_st, v.kind)
VehicleHull.row(v.nid, iz * sp.row * (1.0 - take), -ix * sp.row_turn)
let d = Math.abs(v.speed) * dt let d = Math.abs(v.speed) * dt
if d > 0.0 { ve_moved_add(vehicles_st, v, d) } if d > 0.0 { ve_moved_add(vehicles_st, v, d) }
} }
@ -55,13 +53,14 @@ function ve_swell(vehicles_st: mut VehiclesState, v: Vehicle, iz: float, dt: flo
# called or left, its hull read back and held there # called or left, its hull read back and held there
export function vehicles_tick(vehicles_st: VehiclesState, dt: float) -> void { export function vehicles_tick(vehicles_st: VehiclesState, dt: float) -> void {
let l = ve_all(vehicles_st) let l = ve_all(vehicles_st)
let w = VehicleWorld.wind() * VE_WIND let w = VehicleWorld.wind()
let wy = VehicleWorld.wind_yaw() let wy = VehicleWorld.wind_yaw()
for i in 0 .. len(l) { for i in 0 .. len(l) {
let v = l[i] let v = l[i]
if v.kind != VEHICLE_BOAT { continue } if v.kind != VEHICLE_BOAT { continue }
if v.rider >= 0 { if v.rider >= 0 {
if w > 0.0 { VehicleHull.drift(v.nid, -Math.sin(wy) * w, -Math.cos(wy) * w) } let f = w * vehicle_spec(vehicles_st, v.kind).wind
if f > 0.0 { VehicleHull.drift(v.nid, -Math.sin(wy) * f, -Math.cos(wy) * f) }
continue continue
} }
if not VehicleWorld.active(v.x, v.z) { continue } # outside every zone: left where it lies if not VehicleWorld.active(v.x, v.z) { continue } # outside every zone: left where it lies

View file

@ -0,0 +1,40 @@
# spec.ludic - a kind's numbers, handed in by the game from its own table before any vehicle of the
# kind is made (vehicles_spec): where the rider sits, its pace, its oars, its hay, where one gets off
export property VehicleSpec {
seat_h: float = 0.0 # m: the rider sits this far above it
walk: float = 0.0 # m/s: a horse's walk
run: float = 0.0 # m/s: its gallop
acc: float = 0.0 # m/s2: how fast it gets there
turn: float = 0.0 # rad/s at a full stick
row: float = 0.0 # N: a full stroke forward
row_turn: float = 0.0 # N.m: the oars pulled against each other
wind: float = 0.0 # N on it in a full wind
hay_per_m: float = 0.0 # hay (0..100) every metre costs
hay_min: float = 0.0 # with less hay than this it will not go
run_min: float = 0.0 # stamina (0..100) a gallop needs
tire_at: float = 0.0 # m/s: faster than this is a gallop, and tires it
tire: float = 0.0 # stamina a second spent at a gallop
rest: float = 0.0 # and given back a second at a walk
off_side: float = 0.0 # m: a rider gets off this far out to its right
off_depth: float = 0.0 # m: the deepest wade a rider gets off into
}
export const VEHICLE_KINDS: int = 8 # kinds a game may give numbers to, 0 .. 7
function ve_specs_new() -> []VehicleSpec {
let l = new []VehicleSpec
for i in 0 .. VEHICLE_KINDS { push(l, null) }
return l
}
# kind's numbers from now on; a kind that has none cannot be called
export function vehicles_spec(vehicles_st: mut VehiclesState, kind: int, spec: VehicleSpec) -> void {
if kind < 0 or kind >= VEHICLE_KINDS { return }
vehicles_st.ve_specs[kind] = spec
}
# a kind's numbers, or null when the game gave it none
export function vehicle_spec(vehicles_st: VehiclesState, kind: int) -> VehicleSpec {
if kind < 0 or kind >= VEHICLE_KINDS { return null }
return vehicles_st.ve_specs[kind]
}

View file

@ -158,8 +158,37 @@ program VehiclesTest {
const EAST: float = -1.5707963 const EAST: float = -1.5707963
# Maroon Lake's numbers for its two kinds (its vehicle_kinds.lres), as a game hands them in
function horse_spec() -> VehicleSpec {
let s = new VehicleSpec
s.seat_h = 1.35
s.walk = 4.5
s.run = 11.0
s.acc = 4.0
s.turn = 1.6
s.hay_per_m = 0.02
s.hay_min = 10.0
s.run_min = 5.0
s.tire_at = 6.0
s.tire = 6.0
s.rest = 3.0
s.off_side = 1.2
return s
}
function boat_spec() -> VehicleSpec {
let s = new VehicleSpec
s.seat_h = 0.30
s.row = 630.0
s.row_turn = 270.0
s.wind = 70.0
s.off_depth = 1.25
return s
}
function fresh(vehicles_st: mut VehiclesState, vehicles_test_st: mut VehiclesTestState) -> void { function fresh(vehicles_st: mut VehiclesState, vehicles_test_st: mut VehiclesTestState) -> void {
vehicles_reset(vehicles_st) vehicles_reset(vehicles_st)
vehicles_spec(vehicles_st, VEHICLE_HORSE, horse_spec())
vehicles_spec(vehicles_st, VEHICLE_BOAT, boat_spec())
vehicles_dock_at(vehicles_st, 10.0, 0.0, EAST) vehicles_dock_at(vehicles_st, 10.0, 0.0, EAST)
vehicles_rail_at(vehicles_st, 0.0, 5.0, 0.0) vehicles_rail_at(vehicles_st, 0.0, 5.0, 0.0)
vehicles_test_st.made = 0 vehicles_test_st.made = 0
@ -189,6 +218,27 @@ program VehiclesTest {
expect_near(vehicle_bay_z(vehicles_st, 2), 3.6, 0.01) expect_near(vehicle_bay_z(vehicles_st, 2), 3.6, 0.01)
} }
test "a kind with no numbers cannot be called; its spec's numbers are the horse's pace and its seat" (physics_st: mut PhysicsState, vehicles_st: mut VehiclesState, vehicles_test_st: mut VehiclesTestState) {
fresh(vehicles_st, vehicles_test_st)
vehicles_spec(vehicles_st, VEHICLE_BOAT, null)
expect(not vehicle_call_for(vehicles_st, 0, VEHICLE_BOAT, 0))
let slow = horse_spec()
slow.walk = 2.5
slow.seat_h = 1.1
vehicles_spec(vehicles_st, VEHICLE_HORSE, slow)
expect(vehicle_spec(vehicles_st, VEHICLE_HORSE) == slow)
vehicle_call_for(vehicles_st, 0, VEHICLE_HORSE, 0)
let h = vehicle_find(vehicles_st, VEHICLE_HORSE)
expect_near(h.seat_h, 1.1, 0.001)
vehicle_mount(vehicles_st, h)
ride(physics_st, vehicles_st, 0.0, 1.0, false, 3.0)
expect_near(h.speed, 2.5, 0.05)
expect_near(vehicles_test_st.ry - h.y, 1.1, 0.001)
vehicle_drop(vehicles_st)
vehicles_reset(vehicles_st)
expect(vehicle_spec(vehicles_st, VEHICLE_HORSE) == slow) # a reset keeps the numbers
}
test "the first call makes a vehicle, a later one fetches it, and a taken place refuses" (vehicles_st: mut VehiclesState, vehicles_test_st: mut VehiclesTestState) { test "the first call makes a vehicle, a later one fetches it, and a taken place refuses" (vehicles_st: mut VehiclesState, vehicles_test_st: mut VehiclesTestState) {
fresh(vehicles_st, vehicles_test_st) fresh(vehicles_st, vehicles_test_st)
expect(vehicle_call_for(vehicles_st, 0, VEHICLE_BOAT, 0)) expect(vehicle_call_for(vehicles_st, 0, VEHICLE_BOAT, 0))