merge lang/physics-senses: animals and people slide round still things, the aim's, photo's and animals' sight lines are one Jolt ray

Conflicts with 0.R5's state defaults in ludic.wildlife resolved; wl_step writes the push's answer, so
its callers take WildlifeState mut again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-27 01:23:17 +03:00
commit ac815bf638
28 changed files with 236 additions and 158 deletions

View file

@ -36,14 +36,14 @@ export port PhotoLens {
x, y, z, fx, fy, fz, fov, # the camera
project(x, y, z) -> bool, sx(), sy(), # into the picture, 0..1
visible(x, y, z, r) -> bool, # the frustum
ground(x, z), trunk(x, z, r) -> bool, # what a line of sight walks
clear(ax, ay, az, bx, by, bz) -> bool, # a line of sight: the ground and every still thing
range(), steady() -> bool # the lens in the pack
}
export port PhotoLight { hour, night, moon, dim, bonus, sun_x, sun_z }
export port PhotoRules { behaviour, subject_value(sp), legend_value(sp), mult }
```
Every member has a default: a lens at the origin looking down -z on flat open ground at noon.
Every member has a default: a lens at the origin looking down -z with nothing in the way at noon.
## API

View file

@ -1,6 +1,6 @@
# ports.ludic - what a photograph asks the game: where the lens is and what it can see, the light,
# and the rules of the market. Unbound, the lens sits at the origin looking down -z on flat open
# ground at noon under a clear sky, every point projects to the middle of the frame, and a
# and the rules of the market. Unbound, the lens sits at the origin looking down -z with nothing in
# the way at noon under a clear sky, every point projects to the middle of the frame, and a
# subject's first frame is worth 12 and a legend 120.
export port PhotoLens {
x: fn() -> float = fn pht__zero # the camera's position
@ -14,8 +14,7 @@ export port PhotoLens {
sx: fn() -> float = fn pht__half # 0..1 across
sy: fn() -> float = fn pht__half # 0..1 down
visible: fn(float, float, float, float) -> bool = fn pht__seen # a sphere inside the frustum
ground: fn(float, float) -> float = fn pht__flat
trunk: fn(float, float, float) -> bool = fn pht__clear # (x, z, r): a trunk there
clear: fn(float, float, float, float, float, float) -> bool = fn pht__clear # nothing between two points
range: fn() -> float = fn pht__range # how far the lens reaches, m
steady: fn() -> bool = fn pht__no # on a tripod
}
@ -43,8 +42,7 @@ function pht__fov() -> float { return 1.0 }
function pht__centred(x: float, y: float, z: float) -> bool { return true }
function pht__half() -> float { return 0.5 }
function pht__seen(x: float, y: float, z: float, r: float) -> bool { return true }
function pht__flat(x: float, z: float) -> float { return 0.0 }
function pht__clear(x: float, z: float, r: float) -> bool { return false }
function pht__clear(ax: float, ay: float, az: float, bx: float, by: float, bz: float) -> bool { return true }
function pht__range() -> float { return 100.0 }
function pht__no() -> bool { return false }
function pht__noon() -> float { return 12.0 }

View file

@ -1,27 +1,7 @@
# sight.ludic - in the picture, within reach, and actually seen. A frustum says a subject is in FRONT
# of the lens, not that the lens can see it: the ray walks the ground and the trunks between. It is
# forgiving on purpose - the subject passes if its body or its head is clear.
const PHT_STEP: float = 3.0 # metres between the ray's samples
function pht_ray_point(x: float, y: float, z: float) -> bool {
let cx = PhotoLens.x()
let cy = PhotoLens.y()
let cz = PhotoLens.z()
let d = Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy) + (z - cz) * (z - cz))
if d < 3.0 { return true }
let n = int(Math.clamp(d / PHT_STEP, 6.0, 120.0))
for s in 1 .. n {
let f = float(s) / float(n)
let px = Math.lerp(cx, x, f)
let py = Math.lerp(cy, y, f)
let pz = Math.lerp(cz, z, f)
let g = PhotoLens.ground(px, pz)
# the ground with 35 cm of slack; a trunk once past the lens's own few metres
if g - 0.35 > py { return false }
if d * f > 6.0 and py - g < 7.0 and PhotoLens.trunk(px, pz, 0.45) { return false }
}
return true
}
# of the lens, not that the lens can see it: the line must be clear of the ground and the still
# things (the port's). It is forgiving on purpose - the subject passes if its body or its head is clear.
function pht_ray_point(x: float, y: float, z: float) -> bool { return PhotoLens.clear(PhotoLens.x(), PhotoLens.y(), PhotoLens.z(), x, y, z) }
# is (x, y, z) in the picture, within `far`, and can the lens see it?
export function photo_in_view(x: float, y: float, z: float, far: float) -> bool {

View file

@ -34,7 +34,19 @@ program PhotoTest {
if x < 0.0 and z < -45.0 and z > -50.0 { return 10.0 }
return 0.0
}
function fk_trunk(x: float, z: float, r: float) -> bool { return Math.abs(x - 3.75) < 1.6 and Math.abs(z + 30.0) < 1.6 }
function fk_trunk(x: float, z: float) -> bool { return Math.abs(x - 3.75) < 1.6 and Math.abs(z + 30.0) < 1.6 }
# what a physics ray would say: forty points of the line against the ridge and the trunk
function fk_clear(ax: float, ay: float, az: float, bx: float, by: float, bz: float) -> bool {
let n = 40
for s in 1 .. n {
let f = float(s) / float(n)
let px = Math.lerp(ax, bx, f)
let pz = Math.lerp(az, bz, f)
let g = fk_ground(px, pz)
if g - 0.35 > Math.lerp(ay, by, f) or (Math.lerp(ay, by, f) - g < 7.0 and fk_trunk(px, pz)) { return false }
}
return true
}
function fk_range() -> float { return 80.0 }
function fk_hour(photo_test_st: PhotoTestState) -> float { return photo_test_st.hour }
function fk_night(photo_test_st: PhotoTestState) -> bool { return photo_test_st.night }
@ -44,7 +56,7 @@ program PhotoTest {
function fk_behaviour(photo_test_st: PhotoTestState) -> bool { return photo_test_st.behaviour }
function fk_value(sp: int) -> int { return 10 + sp }
bind PhotoLens { y: fn fk_y, fz: fn fk_fz, fov: fn fk_fov, project: fn fk_project, sx: fn fk_sx, sy: fn fk_sy, ground: fn fk_ground, trunk: fn fk_trunk, range: fn fk_range }
bind PhotoLens { y: fn fk_y, fz: fn fk_fz, fov: fn fk_fov, project: fn fk_project, sx: fn fk_sx, sy: fn fk_sy, clear: fn fk_clear, range: fn fk_range }
bind PhotoLight { hour: fn fk_hour, night: fn fk_night, moon: fn fk_moon, dim: fn fk_dim, sun_z: fn fk_sun_z }
bind PhotoRules { behaviour: fn fk_behaviour, subject_value: fn fk_value }