vehicles: a boat nobody rows is moored where it was called or left - its hull put back on the mooring (home_x, home_z, home_yaw) whenever it strays, and no wind on it; the wind drifts only a rowed boat

A called boat and one got out of drifted across the lake on the wind (the user's play of the bundle).
The tests hold a boat at its berth in a full wind for eight seconds, and one dropped out on the lake
where it was left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 12:12:44 +03:00
parent b2a45c2fda
commit 66da424b90
6 changed files with 64 additions and 13 deletions

View file

@ -26,8 +26,9 @@ through the game).
- **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
the oars pulled against each other - and reads back where the hull is and how fast it goes. The the oars pulled against each other - and reads back where the hull is and how fast it goes. The
water floats it, the wind (`VehicleWorld.wind`) drifts every boat, ridden or not, and the lake bed water floats it, the wind (`VehicleWorld.wind`) drifts a rowed boat, and the lake bed stops it at
stops it at the shore; the swell at the sail limit takes the outward share of a stroke. the shore. A boat nobody rows is moored where it was called or left (`home_x`, `home_z`,
`home_yaw`): its hull is put back there whenever it strays, so it never crosses the lake alone; the swell at the sail limit takes the outward share of a stroke.
- **A horse walks on legs in the game's physics** (`VehicleLegs`; a Jolt walker in Maroon Lake). The - **A horse walks on legs in the game's physics** (`VehicleLegs`; a Jolt walker in Maroon Lake). The
package decides its pace - a walk, a canter on stamina, hay for every metre - and never steps it package decides its pace - a walk, a canter on stamina, hay for every metre - and never steps it
into water; the legs meet the ground, its steps and slopes, and whatever stands in its way. What into water; the legs meet the ground, its steps and slopes, and whatever stands in its way. What
@ -74,7 +75,7 @@ export port VehicleRider {
| `vehicle_mount(v) -> bool`, `vehicle_dismount() -> bool`, `vehicle_drop()` | on and off | | `vehicle_mount(v) -> bool`, `vehicle_dismount() -> bool`, `vehicle_drop()` | on and off |
| `vehicle_update(ix, iz, run, dt)` | a frame of riding from the rider's stick (bind it where the body hands its stick over) | | `vehicle_update(ix, iz, run, dt)` | a frame of riding from the rider's stick (bind it where the body hands its stick over) |
| `vehicle_own(v)`, `vehicle_feed(v, hay)`, `vehicle_follow(v, x, y, z, yaw)`, `vehicles_release(pid)`, `vehicle_restore(kind, owned, food)` | hiring, hay, a tied horse's wandering, a player leaving, a saved word | | `vehicle_own(v)`, `vehicle_feed(v, hay)`, `vehicle_follow(v, x, y, z, yaw)`, `vehicles_release(pid)`, `vehicle_restore(kind, owned, food)` | hiring, hay, a tied horse's wandering, a player leaving, a saved word |
| `vehicles_tick(dt)` | every boat's hull read back and pushed by the wind (the game calls it where the world is run) | | `vehicles_tick(dt)` | every boat's hull read back: a rowed one pushed by the wind, one nobody rows held at its mooring (the game calls it where the world is run) |
| `vehicle_facts() -> Queue<VehicleFact>` | `{ what, nid, kind, owner, x, z, v }`: `VEHICLE_MOUNTED`, `_DISMOUNTED`, `_NO_LANDING`, `_MOVED` (v: metres), `_SWELL` (every 14 s), `_HUNGRY`, `_FED` | | `vehicle_facts() -> Queue<VehicleFact>` | `{ what, nid, kind, owner, x, z, v }`: `VEHICLE_MOUNTED`, `_DISMOUNTED`, `_NO_LANDING`, `_MOVED` (v: metres), `_SWELL` (every 14 s), `_HUNGRY`, `_FED` |
| `vehicles_reset()`, `vehicles_system() -> System` | `"vehicles"`: this player's own saved by kind (hired, hay) - where they lie is not saved | | `vehicles_reset()`, `vehicles_system() -> System` | `"vehicles"`: this player's own saved by kind (hired, hay) - where they lie is not saved |

View file

@ -39,8 +39,14 @@ function ve_put(v: Vehicle, x: float, z: float) -> void {
v.z = z v.z = z
v.speed = 0.0 v.speed = 0.0
if v.kind == VEHICLE_BOAT { v.y = VehicleWorld.water(x, z) + 0.02 } else { v.y = VehicleWorld.ground(x, z) } if v.kind == VEHICLE_BOAT { v.y = VehicleWorld.water(x, z) + 0.02 } else { v.y = VehicleWorld.ground(x, z) }
v.home_x = x ve_moor(v)
v.home_z = z }
# a boat nobody rows holds here (vehicles_tick); a horse is tied here
function ve_moor(v: Vehicle) -> void {
v.home_x = v.x
v.home_z = v.z
v.home_yaw = v.yaw
} }
# Bring player pid's boat or horse to place k. False when the place is somebody else's, or it is # Bring player pid's boat or horse to place k. False when the place is somebody else's, or it is
@ -59,8 +65,8 @@ export function vehicle_call_for(vehicles_st: VehiclesState, pid: int, kind: int
return true return true
} }
if v.rider >= 0 { return false } if v.rider >= 0 { return false }
ve_put(v, vehicle_place_x(vehicles_st, kind, k), vehicle_place_z(vehicles_st, kind, k))
v.yaw = yaw v.yaw = yaw
ve_put(v, vehicle_place_x(vehicles_st, kind, k), vehicle_place_z(vehicles_st, kind, k))
ve_settle(v) ve_settle(v)
VehicleWorld.placed(v) VehicleWorld.placed(v)
return true return true

View file

@ -17,8 +17,9 @@ export property Vehicle {
owned: bool = false # hired or rented for the trip owned: bool = false # hired or rented for the trip
owner: int = 0 # the player it belongs to (0: this machine's) owner: int = 0 # the player it belongs to (0: this machine's)
rider: int = -1 # the player on it, -1 nobody rider: int = -1 # the player on it, -1 nobody
home_x: float = 0.0 # a horse is tied where its home is home_x: float = 0.0 # a horse is tied where its home is; a boat nobody rows is moored there
home_z: float = 0.0 home_z: float = 0.0
home_yaw: float = 0.0 # and a moored boat faces as it was left
ent: int = -1 # its handle in the table ent: int = -1 # its handle in the table
tab: Table<Vehicle> = null tab: Table<Vehicle> = null
} }

View file

@ -28,6 +28,7 @@ export function vehicle_dismount(vehicles_st: mut VehiclesState) -> bool {
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)
} else { } else {
let wx = v.x + Math.cos(v.yaw) * 1.2 let wx = v.x + Math.cos(v.yaw) * 1.2
let wz = v.z - Math.sin(v.yaw) * 1.2 let wz = v.z - Math.sin(v.yaw) * 1.2
@ -49,6 +50,7 @@ export function vehicle_drop(vehicles_st: mut VehiclesState) -> void {
if vehicles_st.ve_cur != null { if vehicles_st.ve_cur != null {
vehicles_st.ve_cur.rider = -1 vehicles_st.ve_cur.rider = -1
vehicles_st.ve_cur.speed = 0.0 vehicles_st.ve_cur.speed = 0.0
if vehicles_st.ve_cur.kind == VEHICLE_BOAT { ve_moor(vehicles_st.ve_cur) }
} }
vehicles_st.ve_cur = null vehicles_st.ve_cur = null
} }

View file

@ -51,19 +51,35 @@ function ve_swell(vehicles_st: mut VehiclesState, v: Vehicle, iz: float, dt: flo
return take return take
} }
# every boat: its hull read back, and the wind pushing it across the lake, ridden or not # every boat: a rowed one pushed by the wind across the lake; one nobody rows moored where it was
# 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() * VE_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 { if v.kind != VEHICLE_BOAT { continue }
if v.rider >= 0 {
if w > 0.0 { VehicleHull.drift(v.nid, -Math.sin(wy) * w, -Math.cos(wy) * w) } if w > 0.0 { VehicleHull.drift(v.nid, -Math.sin(wy) * w, -Math.cos(wy) * w) }
if v.rider < 0 { continue
}
ve_hull_read(v) ve_hull_read(v)
ve_hold(v)
VehicleWorld.placed(v) VehicleWorld.placed(v)
} }
} }
}
# a moored hull that has moved off its mooring (a knock, the swell) is put back on it, at rest
function ve_hold(v: Vehicle) -> void {
let dx = v.x - v.home_x
let dz = v.z - v.home_z
let dyaw = Math.abs(Math.atan2(Math.sin(v.yaw - v.home_yaw), Math.cos(v.yaw - v.home_yaw)))
if dx * dx + dz * dz < 0.0004 and dyaw < 0.01 and Math.abs(v.speed) < 0.01 { return }
v.x = v.home_x
v.z = v.home_z
v.yaw = v.home_yaw
v.y = VehicleWorld.water(v.x, v.z) + 0.02
v.speed = 0.0
VehicleHull.put(v.nid, v.x, v.y, v.z, v.yaw)
} }

View file

@ -309,6 +309,31 @@ program VehiclesTest {
expect(b.z < z0 - 0.2) expect(b.z < z0 - 0.2)
} }
test "a boat nobody rows holds its berth in a wind, and one left out on the lake holds where it was left" (physics_st: mut PhysicsState, vehicles_st: mut VehiclesState, vehicles_test_st: mut VehiclesTestState) {
fresh(vehicles_st, vehicles_test_st)
vehicle_call_for(vehicles_st, 0, VEHICLE_BOAT, 0)
let b = vehicle_find(vehicles_st, VEHICLE_BOAT)
let bx = b.x
let bz = b.z
vehicles_test_st.wind = 1.0
ride(physics_st, vehicles_st, 0.0, 0.0, false, 8.0)
expect_near(b.x, bx, 0.2)
expect_near(b.z, bz, 0.2)
expect(vehicle_in_place(vehicles_st, VEHICLE_BOAT, 0) != null)
vehicles_test_st.wind = 0.0
vehicle_mount(vehicles_st, b)
ride(physics_st, vehicles_st, 0.0, 1.0, false, 4.0)
vehicle_drop(vehicles_st)
let lx = b.x
let lz = b.z
expect(lx > bx + 4.0)
vehicles_test_st.wind = 1.0
ride(physics_st, vehicles_st, 0.0, 0.0, false, 8.0)
expect_near(b.x, lx, 0.2)
expect_near(b.z, lz, 0.2)
expect(Math.abs(b.speed) < 0.05)
}
function char_turn(a: float) -> float { function char_turn(a: float) -> float {
var d = a var d = a
while d > 3.14159265 { d -= 6.2831853 } while d > 3.14159265 { d -= 6.2831853 }