wip(0.S2): migrate - a var nothing writes becomes a let; a state is keyed by its module, directory or program, so every program's plan agrees; paths normalized; program states named SceneDemoState / scene_demo_st; the LSP reads state, mut and entry/handler parameters
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
3eda72e8c2
commit
5ffe50ed02
463 changed files with 72045 additions and 67421 deletions
|
|
@ -6,55 +6,50 @@ export const AIM_REACH_MIN: float = 3.0 # about as far as a hand goes with a
|
|||
export const AIM_USE_MAX: float = 10.0 # past this the crosshair is for looking, not doing
|
||||
export const AIM_NEVER: float = -1.0 # a reach that is never in reach (an animal without a camera)
|
||||
|
||||
export state AimState {
|
||||
am_kind: int = 0
|
||||
am_id: int = -1
|
||||
am_t: float = 10000.0
|
||||
am_dist: float = 0.0
|
||||
am_reach: bool = false
|
||||
am_off: float = 1.0
|
||||
am_seen: int = 0
|
||||
am_blocked: int = 0
|
||||
am_disagree: int = 0
|
||||
}
|
||||
var am_kind: int = 0
|
||||
var am_id: int = -1
|
||||
var am_t: float = 10000.0
|
||||
var am_dist: float = 0.0
|
||||
var am_reach: bool = false
|
||||
var am_off: float = 1.0
|
||||
|
||||
# The crosshair decides WHICH; the distance decides only WHETHER. Every reach is lifted to a floor of
|
||||
# AIM_REACH_MIN, because a per-kind reach chosen against its model was never chosen against standing
|
||||
# between two props and looking at one.
|
||||
export function aim_reach_of(reach: float) -> float { return Math.max(reach, AIM_REACH_MIN) }
|
||||
|
||||
export function aim_begin(aim_st: mut AimState) -> void {
|
||||
aim_st.am_kind = AIM_NONE
|
||||
aim_st.am_id = -1
|
||||
aim_st.am_t = 10000.0
|
||||
aim_st.am_dist = 0.0
|
||||
aim_st.am_reach = false
|
||||
aim_st.am_off = 1.0
|
||||
export function aim_begin() -> void {
|
||||
am_kind = AIM_NONE
|
||||
am_id = -1
|
||||
am_t = 10000.0
|
||||
am_dist = 0.0
|
||||
am_reach = false
|
||||
am_off = 1.0
|
||||
}
|
||||
|
||||
# A candidate of `kind` (the game's own, not AIM_NONE) and `id`: an upright cylinder at (x, z) from y0
|
||||
# to y1 of radius r, within `far` of the body, in reach within `reach` (floored; AIM_NEVER never).
|
||||
# True when it is now the pick.
|
||||
export function aim_offer(aim_st: mut AimState, kind: int, id: int, x: float, y0: float, y1: float, z: float, r: float, reach: float, far: float) -> bool {
|
||||
export function aim_offer(kind: int, id: int, x: float, y0: float, y1: float, z: float, r: float, reach: float, far: float) -> bool {
|
||||
let d = aim_range(x, z)
|
||||
if d > far { return false }
|
||||
let rr = r + aim_slack_m(d)
|
||||
let hit = aim_ray_cyl(x, y0, y1, z, rr)
|
||||
if hit < 0.0 or not (hit < aim_st.am_t) { return false }
|
||||
if hit < 0.0 or not (hit < am_t) { return false }
|
||||
if not aim_clear_to(x, (y0 + y1) * 0.5, z) { return false }
|
||||
aim_st.am_t = hit
|
||||
aim_st.am_off = aim_miss(x, z, rr)
|
||||
aim_st.am_kind = kind
|
||||
aim_st.am_id = id
|
||||
aim_st.am_dist = d
|
||||
aim_st.am_reach = reach >= 0.0 and d < aim_reach_of(reach)
|
||||
am_t = hit
|
||||
am_off = aim_miss(x, z, rr)
|
||||
am_kind = kind
|
||||
am_id = id
|
||||
am_dist = d
|
||||
am_reach = reach >= 0.0 and d < aim_reach_of(reach)
|
||||
return true
|
||||
}
|
||||
|
||||
export function aim_kind(aim_st: AimState) -> int { return aim_st.am_kind }
|
||||
export function aim_id(aim_st: AimState) -> int { return aim_st.am_id }
|
||||
export function aim_dist(aim_st: AimState) -> float { return aim_st.am_dist }
|
||||
export function aim_in_reach(aim_st: AimState) -> bool { return aim_st.am_reach }
|
||||
export function aim_t(aim_st: AimState) -> float { return aim_st.am_t }
|
||||
export function aim_kind() -> int { return am_kind }
|
||||
export function aim_id() -> int { return am_id }
|
||||
export function aim_dist() -> float { return am_dist }
|
||||
export function aim_in_reach() -> bool { return am_reach }
|
||||
export function aim_t() -> float { return am_t }
|
||||
# how well centred the pick is: 0 dead centre, 1 at its (forgiven) edge
|
||||
export function aim_off_centre(aim_st: AimState) -> float { return aim_st.am_off }
|
||||
export function aim_off_centre() -> float { return am_off }
|
||||
|
|
|
|||
|
|
@ -3,16 +3,19 @@
|
|||
# the ground under each line against aim_clear_to. Lines the ground merely grazes are left out: that
|
||||
# is the probe's sampling against the aim's, not a disagreement. Moves the camera (AimView's) through
|
||||
# `look`, which the game binds to put its camera at a point.
|
||||
var am_seen: int = 0
|
||||
var am_blocked: int = 0
|
||||
var am_disagree: int = 0
|
||||
|
||||
export function aim_probe_seen(aim_st: AimState) -> int { return aim_st.am_seen }
|
||||
export function aim_probe_blocked(aim_st: AimState) -> int { return aim_st.am_blocked }
|
||||
export function aim_probe_disagree(aim_st: AimState) -> int { return aim_st.am_disagree }
|
||||
export function aim_probe_seen() -> int { return am_seen }
|
||||
export function aim_probe_blocked() -> int { return am_blocked }
|
||||
export function aim_probe_disagree() -> int { return am_disagree }
|
||||
|
||||
# true when at least one line is hidden and the aim disagrees with the ground on at most one in fifty
|
||||
export function aim_probe_ground(aim_st: mut AimState, px: float, pz: float, look: fn(float, float, float) -> void) -> bool {
|
||||
aim_st.am_seen = 0
|
||||
aim_st.am_blocked = 0
|
||||
aim_st.am_disagree = 0
|
||||
export function aim_probe_ground(px: float, pz: float, look: fn(float, float, float) -> void) -> bool {
|
||||
am_seen = 0
|
||||
am_blocked = 0
|
||||
am_disagree = 0
|
||||
for sp in 0 .. 9 {
|
||||
var sx = px
|
||||
var sz = pz
|
||||
|
|
@ -21,12 +24,12 @@ export function aim_probe_ground(aim_st: mut AimState, px: float, pz: float, loo
|
|||
sx = px + Math.sin(sa) * 250.0
|
||||
sz = pz + Math.cos(sa) * 250.0
|
||||
}
|
||||
if AimView.dry(sx, sz) { am_probe_from(aim_st, sx, AimView.ground(sx, sz) + 1.6, sz, look) }
|
||||
if AimView.dry(sx, sz) { am_probe_from(sx, AimView.ground(sx, sz) + 1.6, sz, look) }
|
||||
}
|
||||
return aim_st.am_blocked >= 1 and not (aim_st.am_disagree > aim_st.am_seen / 50)
|
||||
return am_blocked >= 1 and not (am_disagree > am_seen / 50)
|
||||
}
|
||||
|
||||
function am_probe_from(aim_st: mut AimState, sx: float, sy: float, sz: float, look: fn(float, float, float) -> void) -> void {
|
||||
function am_probe_from(sx: float, sy: float, sz: float, look: fn(float, float, float) -> void) -> void {
|
||||
for di in 0 .. 3 {
|
||||
var dist = 60.0
|
||||
if di == 1 { dist = 120.0 }
|
||||
|
|
@ -39,10 +42,10 @@ function am_probe_from(aim_st: mut AimState, sx: float, sy: float, sz: float, lo
|
|||
let truth = am_probe_gap(sx, sy, sz, ex, ey, ez, 0.5, 10000.0)
|
||||
if am_probe_gap(sx, sy, sz, ex, ey, ez, -0.5, 0.5) and not truth { continue }
|
||||
look(sx, sy, sz)
|
||||
aim_st.am_seen += 1
|
||||
am_seen += 1
|
||||
let clear = aim_clear_to(ex, ey, ez)
|
||||
if not clear { aim_st.am_blocked += 1 }
|
||||
if clear == truth { aim_st.am_disagree += 1 }
|
||||
if not clear { am_blocked += 1 }
|
||||
if clear == truth { am_disagree += 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,36 +6,34 @@ import "ludic.base"
|
|||
program AimTest {
|
||||
numbers float
|
||||
|
||||
state AimtestState {
|
||||
cx: float = 0.0
|
||||
cy: float = 1.5
|
||||
cz: float = 0.0
|
||||
fy: float = 0.0
|
||||
}
|
||||
function fk_x(aimtest_st: AimtestState) -> float { return aimtest_st.cx }
|
||||
function fk_y(aimtest_st: AimtestState) -> float { return aimtest_st.cy }
|
||||
function fk_z(aimtest_st: AimtestState) -> float { return aimtest_st.cz }
|
||||
function fk_fy(aimtest_st: AimtestState) -> float { return aimtest_st.fy }
|
||||
function fk_fz(aimtest_st: AimtestState) -> float { return -Math.sqrt(1.0 - aimtest_st.fy * aimtest_st.fy) }
|
||||
var cx: float = 0.0
|
||||
var cy: float = 1.5
|
||||
var cz: float = 0.0
|
||||
var fy: float = 0.0
|
||||
function fk_x() -> float { return cx }
|
||||
function fk_y() -> float { return cy }
|
||||
function fk_z() -> float { return cz }
|
||||
function fk_fy() -> float { return fy }
|
||||
function fk_fz() -> float { return -Math.sqrt(1.0 - fy * fy) }
|
||||
# a 6 m ridge along z = -42, thirty metres across, the whole width of the view
|
||||
function fk_ground(x: float, z: float) -> float { return 6.0 * Math.max(0.0, 1.0 - Math.abs(z + 42.0) / 15.0) }
|
||||
function fk_look(aimtest_st: mut AimtestState, x: float, y: float, z: float) -> void {
|
||||
aimtest_st.cx = x
|
||||
aimtest_st.cy = y
|
||||
aimtest_st.cz = z
|
||||
function fk_look(x: float, y: float, z: float) -> void {
|
||||
cx = x
|
||||
cy = y
|
||||
cz = z
|
||||
}
|
||||
bind AimView { x: fn fk_x, y: fn fk_y, z: fn fk_z, fy: fn fk_fy, fz: fn fk_fz, ground: fn fk_ground }
|
||||
|
||||
function fresh(aim_st: mut AimState, aimtest_st: mut AimtestState) -> void {
|
||||
aimtest_st.cx = 0.0
|
||||
aimtest_st.cy = 1.5
|
||||
aimtest_st.cz = 0.0
|
||||
aimtest_st.fy = 0.0
|
||||
aim_begin(aim_st)
|
||||
function fresh() -> void {
|
||||
cx = 0.0
|
||||
cy = 1.5
|
||||
cz = 0.0
|
||||
fy = 0.0
|
||||
aim_begin()
|
||||
}
|
||||
|
||||
test "the ray enters a cylinder in front and misses one to the side or behind" (aim_st: mut AimState, aimtest_st: mut AimtestState) {
|
||||
fresh(aim_st, aimtest_st)
|
||||
test "the ray enters a cylinder in front and misses one to the side or behind" {
|
||||
fresh()
|
||||
expect_near(aim_ray_cyl(0.0, 0.0, 2.0, -10.0, 0.5), 9.5, 0.01)
|
||||
expect(aim_ray_cyl(3.0, 0.0, 2.0, -10.0, 0.5) < 0.0)
|
||||
expect(aim_ray_cyl(0.0, 0.0, 2.0, 10.0, 0.5) < 0.0)
|
||||
|
|
@ -43,61 +41,61 @@ program AimTest {
|
|||
expect_near(aim_ray_cyl(0.0, 0.0, 2.0, 0.0, 0.5), 0.0, 0.001)
|
||||
}
|
||||
|
||||
test "a thin thing far off is still aimable, by an angle and not a metre" (aim_st: mut AimState, aimtest_st: mut AimtestState) {
|
||||
fresh(aim_st, aimtest_st)
|
||||
test "a thin thing far off is still aimable, by an angle and not a metre" {
|
||||
fresh()
|
||||
expect(aim_slack_m(20.0) > aim_slack_m(5.0) * 3.9)
|
||||
expect(aim_offer(aim_st, 1, 7, 0.25, 0.0, 2.0, -20.0, 0.05, 1.5, 30.0))
|
||||
expect(aim_off_centre(aim_st) > 0.5)
|
||||
fresh(aim_st, aimtest_st)
|
||||
expect(not aim_offer(aim_st, 1, 7, 3.0, 0.0, 2.0, -20.0, 0.05, 1.5, 30.0))
|
||||
expect(aim_offer(1, 7, 0.25, 0.0, 2.0, -20.0, 0.05, 1.5, 30.0))
|
||||
expect(aim_off_centre() > 0.5)
|
||||
fresh()
|
||||
expect(not aim_offer(1, 7, 3.0, 0.0, 2.0, -20.0, 0.05, 1.5, 30.0))
|
||||
}
|
||||
|
||||
test "the nearest thing the ray enters is the pick, whatever order they are offered in" (aim_st: mut AimState, aimtest_st: mut AimtestState) {
|
||||
fresh(aim_st, aimtest_st)
|
||||
expect(aim_offer(aim_st, 1, 1, 0.0, 0.0, 2.0, -20.0, 0.5, 1.5, 30.0))
|
||||
expect(aim_offer(aim_st, 1, 2, 0.0, 0.0, 2.0, -8.0, 0.5, 1.5, 30.0))
|
||||
expect(not aim_offer(aim_st, 1, 3, 0.0, 0.0, 2.0, -15.0, 0.5, 1.5, 30.0))
|
||||
expect_eq(aim_id(aim_st), 2)
|
||||
expect_near(aim_dist(aim_st), 8.0, 0.01)
|
||||
expect(not aim_offer(aim_st, 2, 4, 0.0, 0.0, 2.0, -25.0, 0.5, 1.5, 30.0))
|
||||
expect(not aim_offer(aim_st, 2, 5, 0.0, 0.0, 2.0, -3.0, 0.5, 1.5, 2.0))
|
||||
test "the nearest thing the ray enters is the pick, whatever order they are offered in" {
|
||||
fresh()
|
||||
expect(aim_offer(1, 1, 0.0, 0.0, 2.0, -20.0, 0.5, 1.5, 30.0))
|
||||
expect(aim_offer(1, 2, 0.0, 0.0, 2.0, -8.0, 0.5, 1.5, 30.0))
|
||||
expect(not aim_offer(1, 3, 0.0, 0.0, 2.0, -15.0, 0.5, 1.5, 30.0))
|
||||
expect_eq(aim_id(), 2)
|
||||
expect_near(aim_dist(), 8.0, 0.01)
|
||||
expect(not aim_offer(2, 4, 0.0, 0.0, 2.0, -25.0, 0.5, 1.5, 30.0))
|
||||
expect(not aim_offer(2, 5, 0.0, 0.0, 2.0, -3.0, 0.5, 1.5, 2.0))
|
||||
}
|
||||
|
||||
test "the ground in front hides a thing, and nothing within arm's length is hidden" (aim_st: mut AimState, aimtest_st: mut AimtestState) {
|
||||
fresh(aim_st, aimtest_st)
|
||||
test "the ground in front hides a thing, and nothing within arm's length is hidden" {
|
||||
fresh()
|
||||
expect(not aim_clear_to(0.0, 1.0, -60.0))
|
||||
expect(aim_clear_to(0.0, 1.0, -30.0))
|
||||
expect(not aim_offer(aim_st, 1, 1, 0.0, 0.0, 2.0, -60.0, 2.0, 1.5, 80.0))
|
||||
aimtest_st.cy = -5.0
|
||||
expect(not aim_offer(1, 1, 0.0, 0.0, 2.0, -60.0, 2.0, 1.5, 80.0))
|
||||
cy = -5.0
|
||||
expect(aim_clear_to(0.0, -5.0, -2.0))
|
||||
}
|
||||
|
||||
test "the crosshair decides which, the distance whether: every reach is floored at three metres" (aim_st: mut AimState, aimtest_st: mut AimtestState) {
|
||||
fresh(aim_st, aimtest_st)
|
||||
test "the crosshair decides which, the distance whether: every reach is floored at three metres" {
|
||||
fresh()
|
||||
expect_near(aim_reach_of(1.5), AIM_REACH_MIN, 0.001)
|
||||
expect_near(aim_reach_of(3.4), 3.4, 0.001)
|
||||
aim_offer(aim_st, 1, 1, 0.0, 0.0, 2.0, -2.6, 0.3, 1.5, 30.0)
|
||||
expect(aim_in_reach(aim_st))
|
||||
expect_eq(aim_use(aim_st, 1), AIM_USE_THIS)
|
||||
fresh(aim_st, aimtest_st)
|
||||
aim_offer(aim_st, 1, 1, 0.0, 0.0, 2.0, -6.0, 0.3, 1.5, 30.0)
|
||||
expect(not aim_in_reach(aim_st))
|
||||
expect_eq(aim_use(aim_st, 1), AIM_USE_NOTHING)
|
||||
expect_eq(aim_use(aim_st, 3), AIM_USE_NEAREST)
|
||||
fresh(aim_st, aimtest_st)
|
||||
aim_offer(aim_st, 1, 1, 0.0, 0.0, 3.0, -20.0, 0.3, 1.5, 30.0)
|
||||
expect_eq(aim_use(aim_st, 1), AIM_USE_NEAREST)
|
||||
fresh(aim_st, aimtest_st)
|
||||
aim_offer(aim_st, 2, 9, 0.0, 0.0, 2.0, -2.0, 0.3, AIM_NEVER, 30.0)
|
||||
expect(not aim_in_reach(aim_st))
|
||||
expect_eq(aim_kind(aim_st), 2)
|
||||
aim_offer(1, 1, 0.0, 0.0, 2.0, -2.6, 0.3, 1.5, 30.0)
|
||||
expect(aim_in_reach())
|
||||
expect_eq(aim_use(1), AIM_USE_THIS)
|
||||
fresh()
|
||||
aim_offer(1, 1, 0.0, 0.0, 2.0, -6.0, 0.3, 1.5, 30.0)
|
||||
expect(not aim_in_reach())
|
||||
expect_eq(aim_use(1), AIM_USE_NOTHING)
|
||||
expect_eq(aim_use(3), AIM_USE_NEAREST)
|
||||
fresh()
|
||||
aim_offer(1, 1, 0.0, 0.0, 3.0, -20.0, 0.3, 1.5, 30.0)
|
||||
expect_eq(aim_use(1), AIM_USE_NEAREST)
|
||||
fresh()
|
||||
aim_offer(2, 9, 0.0, 0.0, 2.0, -2.0, 0.3, AIM_NEVER, 30.0)
|
||||
expect(not aim_in_reach())
|
||||
expect_eq(aim_kind(), 2)
|
||||
}
|
||||
|
||||
test "the probe finds the bank and agrees with the ground about every line it hides" (aim_st: mut AimState, aimtest_st: mut AimtestState) {
|
||||
fresh(aim_st, aimtest_st)
|
||||
expect(aim_probe_ground(aim_st, 0.0, 0.0, fn fk_look))
|
||||
expect(aim_probe_seen(aim_st) > 100)
|
||||
expect(aim_probe_blocked(aim_st) > 0)
|
||||
expect(not (aim_probe_disagree(aim_st) > aim_probe_seen(aim_st) / 50))
|
||||
test "the probe finds the bank and agrees with the ground about every line it hides" {
|
||||
fresh()
|
||||
expect(aim_probe_ground(0.0, 0.0, fn fk_look))
|
||||
expect(aim_probe_seen() > 100)
|
||||
expect(aim_probe_blocked() > 0)
|
||||
expect(not (aim_probe_disagree() > aim_probe_seen() / 50))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ export const AIM_USE_THIS: int = 0
|
|||
export const AIM_USE_NOTHING: int = 1
|
||||
export const AIM_USE_NEAREST: int = 2
|
||||
|
||||
export function aim_use(aim_st: AimState, kind: int) -> int {
|
||||
if aim_st.am_kind == kind and aim_st.am_reach { return AIM_USE_THIS }
|
||||
if aim_st.am_kind == kind and aim_st.am_dist < AIM_USE_MAX { return AIM_USE_NOTHING }
|
||||
export function aim_use(kind: int) -> int {
|
||||
if am_kind == kind and am_reach { return AIM_USE_THIS }
|
||||
if am_kind == kind and am_dist < AIM_USE_MAX { return AIM_USE_NOTHING }
|
||||
return AIM_USE_NEAREST
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue