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,71 @@
# port.ludic - what the people ask the place. Unbound: a flat dry field at noon on day one, one
# player at the origin, nobody wanted, every name free, and nothing drawn
export port NpcWorld {
ground: fn(float, float) -> float = fn nw_zero2
water: fn(float, float) -> float = fn nw_dry # the water's surface there
slope: fn(float, float) -> float = fn nw_zero2
allowed: fn(float, float) -> bool = fn nw_yes2 # a person may stop here (not the player's camp)
hour: fn() -> float = fn nw_noon
day: fn() -> int = fn nw_day_one
players: fn() -> int = fn nw_one # player slots
player_on: fn(int) -> bool = fn nw_first
player_x: fn(int) -> float = fn nw_zero1
player_z: fn(int) -> float = fn nw_zero1
want: fn() -> int = fn nw_none # walkers the place wants at this hour
extra: fn() -> int = fn nw_no_kind # a kind to bring out whatever the crowd (-1)
origin_x: fn() -> float = fn nw_zero0 # where anyone is put when nowhere else will do
origin_z: fn() -> float = fn nw_zero0
next_id: fn() -> int = fn nw_next_id
body_of: fn(int) -> int = fn nw_body_of # a look seed's body
taken: fn(string) -> bool = fn nw_free # a name the place already uses
born: fn(NpcPerson) -> void = fn nw_nothing # a new one (or a slot reused): draw it
gone: fn(NpcPerson) -> void = fn nw_nothing # left: stop drawing it
}
# what the story says through them: the words of a line whose `fill` is set ("" when it has nothing
# to say now), and a greeting (a vendor's, when talked to)
export port NpcTalk {
say: fn(NpcPerson, int) -> string = fn nw_silent
greeting: fn(NpcPerson) -> string = fn nw_hello
}
function nw_zero0() -> float { return 0.0 }
function nw_zero1(p: int) -> float { return 0.0 }
function nw_zero2(x: float, z: float) -> float { return 0.0 }
function nw_dry(x: float, z: float) -> float { return -1000.0 }
function nw_yes2(x: float, z: float) -> bool { return true }
function nw_noon() -> float { return 12.0 }
function nw_day_one() -> int { return 1 }
function nw_one() -> int { return 1 }
function nw_first(p: int) -> bool { return p == 0 }
function nw_none() -> int { return 0 }
function nw_no_kind() -> int { return -1 }
function nw_body_of(look: int) -> int { return look % 2 }
function nw_free(n: string) -> bool { return false }
function nw_nothing(p: NpcPerson) -> void { }
function nw_silent(p: NpcPerson, line: int) -> string { return "" }
function nw_hello(p: NpcPerson) -> string { return "" }
var nw_ids: int = 0
function nw_next_id() -> int {
nw_ids += 1
return nw_ids
}
# the nearest player on and their distance (npc_near_i: -1 when nobody is on)
var np_near_i: int = -1
export function npc_near(x: float, z: float) -> float {
var bd = 1000000.0
np_near_i = -1
for i in 0 .. NpcWorld.players() {
if not NpcWorld.player_on(i) { continue }
let d = npc_d(x, z, NpcWorld.player_x(i), NpcWorld.player_z(i))
if d < bd {
bd = d
np_near_i = i
}
}
return bd
}
export function npc_near_i() -> int { return np_near_i }