feat(packages): ludic.npc - the other people in a place: kinds, spots, acts, names and lines as open registries (a kind's routine is steps by the hour - a spot, an act, how long - with its own start and plan as function values, readable from .lres), a census on the half hour by weight (after dark only who stays out) with an extra the place asks for, walking round what is in the way and never into the water or up a scramble, no act where the place says nobody stops, facing a player within notice and back to the errand, leaving only out of every player's sight, unique names, posts that never walk (a vendor), lines by prio and chance with the story's words through a port and choices, a guest's copy of a host's people; greeted, talked and arrived as facts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 09:59:07 +03:00
parent 73a376ac6c
commit 029a1ebba2
23 changed files with 1664 additions and 0 deletions

View file

@ -0,0 +1,101 @@
# ground.ludic - where a person can stand and walk: dry, gentle, allowed; a straight walk with no
# water and no scramble on it; a spot in a ring, a spot toward something, and the nearest shore.
# A finder that succeeds leaves its answer in npc_px / npc_pz (and the water in npc_wx / npc_wz).
var np_px: float = 0.0
var np_pz: float = 0.0
var np_wx: float = 0.0
var np_wz: float = 0.0
export function npc_px() -> float { return np_px }
export function npc_pz() -> float { return np_pz }
export function npc_wx() -> float { return np_wx }
export function npc_wz() -> float { return np_wz }
export function npc_walkable(x: float, z: float) -> bool {
if not NpcWorld.allowed(x, z) { return false }
if NpcWorld.ground(x, z) < NpcWorld.water(x, z) + 0.6 { return false }
return NpcWorld.slope(x, z) < 0.75
}
export function npc_line_ok(ax: float, az: float, bx: float, bz: float) -> bool {
let d = npc_d(ax, az, bx, bz)
var n = int(d / 1.5)
if n < 1 { n = 1 }
var prev = NpcWorld.ground(ax, az)
for i in 1 .. n + 1 {
let t = float(i) / float(n)
let x = ax + (bx - ax) * t
let z = az + (bz - az) * t
let h = NpcWorld.ground(x, z)
if h < NpcWorld.water(x, z) + 0.5 { return false }
if h - prev > d / float(n) * 1.3 { return false }
prev = h
}
return true
}
function np_found(x: float, z: float) -> bool {
np_px = x
np_pz = z
return true
}
# somewhere to stand d0 .. d1 from a point
export function npc_spot_near(x0: float, z0: float, d0: float, d1: float) -> bool {
for t in 0 .. 40 {
let ang = npc_rnd() * NP_TAU
let d = d0 + npc_rnd() * (d1 - d0)
let x = x0 + Math.cos(ang) * d
let z = z0 + Math.sin(ang) * d
if npc_walkable(x, z) { return np_found(x, z) }
}
return false
}
# a place to stand d from (ax, az), on the side (bx, bz) is on
export function npc_toward(ax: float, az: float, bx: float, bz: float, d: float) -> bool {
let dd = Math.max(npc_d(ax, az, bx, bz), 0.5)
let x = ax + (bx - ax) / dd * d
let z = az + (bz - az) / dd * d
if npc_walkable(x, z) { return np_found(x, z) }
for k in 0 .. 12 {
let ang = Math.atan2(bz - az, bx - ax) + float((k + 1) / 2 * (1 - 2 * (k % 2))) * 0.25
let x2 = ax + Math.cos(ang) * d
let z2 = az + Math.sin(ang) * d
if npc_walkable(x2, z2) { return np_found(x2, z2) }
}
return npc_spot_near(x, z, 0.0, 14.0)
}
# the nearest bit of shore within rmax: where to stand, and the water in front of it
export function npc_shore(x0: float, z0: float, rmax: float) -> bool {
var found = false
var bd = rmax
for k in 0 .. 16 {
let ang = float(k) * (NP_TAU / 16.0)
var lx = x0
var lz = z0
var ld = -100.0
var d = 2.0
while d < bd {
let x = x0 + Math.cos(ang) * d
let z = z0 + Math.sin(ang) * d
if NpcWorld.ground(x, z) < NpcWorld.water(x, z) {
if ld > 0.0 and d - ld < 12.0 {
bd = d
found = np_found(lx, lz)
np_wx = x
np_wz = z
}
break
}
if npc_walkable(x, z) {
lx = x
lz = z
ld = d
}
d = d + 2.0
}
}
return found
}