ludic/packages/ludic.npc/tests/npc_test.ludic
Orkuncakilkaya 71735b10a2 feat(base): 0.R4 - a queue keeps its own count, so every package verb takes only its own state; ludic migrate state --prune
ludic.base's Queue<T> carries a QueueTag (its name and pending count): queue_new(name), q_push(q, v),
q_drain(q), q_clear(q) take no BaseState, and core_undrained(tags) names the given queues still holding
facts. A reducer on a package's state can now call that package's verbs (wallet_earn(wallet_st, n)).

ludic migrate state --prune (ludicc --migrate-prune) takes out each state parameter a function no
longer uses, nor anything it calls, and the argument that fills it - including an argument for a
parameter the callee has dropped, which is taken out before the call is checked, so a generic's T is
told by the argument that says it. A reducer keeps its state. --dry-run now counts the edits it would
make. Every package was moved with it: 608 base_st parameters and their arguments, 1889 edits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 04:19:56 +03:00

232 lines
8.9 KiB
Text

# npc_test.ludic - the census by the hour and by weight, unique names, walking to a spot and never
# into the water, a routine's steps by the hour, turning to a player and greeting them once, leaving
# out of sight, lines and choices, a post, and a guest's copy
import "ludic.npc"
import "ludic.base"
import "fake"
program NpcTest {
numbers float
bind NpcWorld {
ground: fn fk_ground
water: fn fk_water
allowed: fn fk_allowed
hour: fn fk_hour_now
day: fn fk_day_now
players: fn fk_players
player_on: fn fk_player_on
player_x: fn fk_x
player_z: fn fk_z
want: fn fk_wanted
extra: fn fk_extra_kind
taken: fn fk_taken
born: fn fk_on_born
gone: fn fk_on_gone
}
bind NpcTalk { say: fn tk_say, greeting: fn tk_greeting }
def NpcActs sit { }
def NpcActs fish { turns: false }
def NpcKinds from "kinds.lres"
def NpcNames from "names.lres"
def NpcLines from "lines.lres"
state NpcTestState {
news_on: bool = false
}
function tk_say(npc_test_st: NpcTestState, p: NpcPerson, line: int) -> string {
if npc_test_st.news_on { return `{p.name} saw a bear.` }
return ""
}
function tk_greeting(p: NpcPerson) -> string { return "What'll it be?" }
function tk_start(npc_st: mut NpcState, p: NpcPerson) -> void { npc_start_near(npc_st, p, 0.0, 0.0, 0.0, 10.0) }
function one(npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState, kind: int) -> NpcPerson {
fk_setup(npc_st, npc_tests_fake_st)
npc_tests_fake_st.fk_want = 1
let p = npc_spawn(npc_st, kind)
npc_set_at(p, 0.0, 0.0)
return p
}
test "the tables: the package's acts and spots, and the game's kinds, names and lines" {
expect_eq(NPCA_WALK, 0)
expect_eq(NPCA_IDLE, 1)
expect_eq(npcspots_find("shore_here"), NPCS_SHORE_HERE)
expect_eq(NPCK_COUNT, 3)
expect(NpcKinds[NPCK_CAMPER].stays)
expect_eq(len(NpcKinds[NPCK_HIKER].steps), 3)
expect_eq(NPCN_COUNT, 14)
}
test "the census fills to what the place wants, by weight, and after dark only who stays out" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
fk_setup(npc_st, npc_tests_fake_st)
npc_tests_fake_st.fk_want = 4
npc_tick(npc_st, 0.05)
expect_eq(npc_count(npc_st), 4)
expect_eq(npc_tests_fake_st.fk_born, 4)
expect_eq(npc_name_clashes(npc_st), 0)
for i in 0 .. len(npc_walkers(npc_st)) { expect(npc_walkers(npc_st)[i].kind != NPCK_VENDOR) }
fk_setup(npc_st, npc_tests_fake_st)
npc_tests_fake_st.fk_want = 3
npc_tests_fake_st.fk_hour = 23.0
npc_tick(npc_st, 0.05)
expect_eq(npc_count(npc_st), 3)
for i in 0 .. len(npc_walkers(npc_st)) { expect_eq(npc_walkers(npc_st)[i].kind, NPCK_CAMPER) }
}
test "no name twice, and never one the place keeps" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
fk_setup(npc_st, npc_tests_fake_st)
npc_tests_fake_st.fk_want = 6
npc_tick(npc_st, 0.05)
expect_eq(npc_count(npc_st), 6)
expect_eq(npc_name_clashes(npc_st), 0)
for i in 0 .. 6 { expect(npc_walkers(npc_st)[i].name != "Ada") }
}
test "it walks to where it is going and begins there, facing what it came for" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_go(npc_st, p, 20.0, 0.0, NPCA_SIT, 30.0)
npc_face(p, 30.0, 0.0)
fk_run(npc_st, 30.0)
expect_eq(p.act, NPCA_SIT)
expect(npc_d(p.x, p.z, 20.0, 0.0) < 1.3)
expect(p.phase > 20.0)
expect_eq(fk_count(npc_st, NPC_F_ARRIVED), 1)
expect_near(p.fx, 30.0, 0.01)
}
test "nobody walks into the lake" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_go(npc_st, p, -80.0, 0.0, NPCA_SIT, 30.0)
var west = 0.0
for k in 0 .. 1200 {
npc_tick(npc_st, 0.05)
west = Math.min(west, p.x)
}
expect(west > -50.5)
}
test "a step's spot is where its hours say: the shore by day, home by night" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(npc_st, npc_tests_fake_st, NPCK_CAMPER)
npc_set_at(p, -20.0, 0.0)
npc_tests_fake_st.fk_hour = 12.0
npc_plan(npc_st, p)
expect_eq(p.act, NPCA_WALK)
expect_eq(p.next_act, NPCA_FISH)
expect(p.fx < -50.0)
npc_tests_fake_st.fk_hour = 23.0
npc_plan(npc_st, p)
expect_eq(p.act, NPCA_SIT)
}
test "an act is not begun where the place says nobody stops" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_set_at(p, 40.0, 0.0)
npc_do(npc_st, p, NPCA_SIT, 30.0)
expect_eq(p.act, NPCA_WALK)
expect(fk_allowed(p.tx, p.tz))
}
test "it turns to a player who comes near, greets them once, and again after they have gone" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_do(npc_st, p, NPCA_IDLE, 100.0)
p.yaw = 0.0
fk_player_at(npc_tests_fake_st, 0.0, 0.0 - 5.0)
fk_run(npc_st, 2.0)
expect(Math.abs(p.yaw) < 0.05)
fk_player_at(npc_tests_fake_st, 5.0, 0.0)
fk_run(npc_st, 3.0)
expect(Math.abs(npc_wrap(p.yaw + 1.5707963)) < 0.05)
fk_run(npc_st, 1.0)
expect_eq(fk_count(npc_st, NPC_F_GREETED), 1)
fk_player_at(npc_tests_fake_st, 40.0, 0.0)
fk_run(npc_st, 0.5)
fk_player_at(npc_tests_fake_st, 5.0, 0.0)
fk_run(npc_st, 0.5)
expect_eq(fk_count(npc_st, NPC_F_GREETED), 1)
}
test "a fisher keeps its eyes on the water, whoever is near" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_do(npc_st, p, NPCA_FISH, 100.0)
p.yaw = 0.0
npc_face(p, 0.0, -10.0)
fk_player_at(npc_tests_fake_st, 5.0, 0.0)
fk_run(npc_st, 2.0)
expect(Math.abs(p.yaw) < 0.05)
}
test "the day ends out of sight, and not in it" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(npc_st, npc_tests_fake_st, NPCK_HIKER)
npc_do(npc_st, p, NPCA_IDLE, 1000.0)
npc_tests_fake_st.fk_hour = 22.5
fk_player_at(npc_tests_fake_st, 20.0, 0.0)
fk_run(npc_st, 0.5)
expect(p.active)
fk_player_at(npc_tests_fake_st, 500.0, 0.0)
fk_run(npc_st, 0.5)
expect(not p.active)
expect_eq(npc_tests_fake_st.fk_gone, 1)
let c = one(npc_st, npc_tests_fake_st, NPCK_CAMPER)
npc_do(npc_st, c, NPCA_IDLE, 1000.0)
npc_tests_fake_st.fk_hour = 22.5
fk_run(npc_st, 0.5)
expect(c.active)
}
test "lines: the highest that has something to say, the story's words, and choices" (npc_st: mut NpcState, npc_test_st: mut NpcTestState, npc_tests_fake_st: mut NpcTestsFakeState) {
let p = one(npc_st, npc_tests_fake_st, NPCK_CAMPER)
npc_do(npc_st, p, NPCA_FISH, 100.0)
expect_eq(npc_talk(npc_st, p, 0), NPCL_HUSH)
expect(npc_said(npc_st) == "Shh.")
expect(npc_talked_today(p))
npc_do(npc_st, p, NPCA_IDLE, 100.0)
expect_eq(npc_line_for(npc_st, p), NPCL_HELLO)
npc_test_st.news_on = true
expect_eq(npc_line_for(npc_st, p), NPCL_NEWS)
expect(npc_said(npc_st) == `{p.name} saw a bear.`)
npc_test_st.news_on = false
expect_eq(npc_choose(npc_st, p, NPCL_HELLO, 0, 0), NPCL_FISH_NO)
expect(npc_said(npc_st) == "Not today.")
expect_eq(npc_choose(npc_st, p, NPCL_HELLO, 1, 0), -1)
let fs = q_drain(npc_facts(npc_st))
expect_eq(len(fs), 3)
expect_eq(fs[1].choice, 7)
expect(npc_greeting(p) == "What'll it be?")
}
test "a post stands where it was put, is not counted, and turns back when nobody is near" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
fk_setup(npc_st, npc_tests_fake_st)
let v = npc_place(npc_st, NPCK_VENDOR, 0.0, 0.0, 0.0, "Mateo", 5)
expect(v.fixed)
expect_eq(npc_count(npc_st), 0)
fk_player_at(npc_tests_fake_st, 8.0, 0.0)
fk_run(npc_st, 3.0)
expect(Math.abs(npc_wrap(v.yaw + 1.5707963)) < 0.05)
fk_player_at(npc_tests_fake_st, 300.0, 0.0)
fk_run(npc_st, 3.0)
expect(Math.abs(v.yaw) < 0.05)
expect(npc_d(v.x, v.z, 0.0, 0.0) < 0.001)
expect(npc_by_id(npc_st, v.id).name == "Mateo")
}
test "a guest's copy: the host's slot put, heard and eased toward, and not drawn once it goes quiet" (npc_st: mut NpcState, npc_tests_fake_st: mut NpcTestsFakeState) {
fk_setup(npc_st, npc_tests_fake_st)
npc_tests_fake_st.fk_want = 2
npc_tick(npc_st, 0.05)
npc_mirror(npc_st, true)
expect_eq(npc_count(npc_st), 0)
let p = npc_put(npc_st, 3, 99, NPCK_HIKER, 12, 1.5, "Wren", 10.0, 10.0)
expect(p.active)
expect_eq(npc_count(npc_st), 1)
npc_heard(npc_st, 3, 10.0, 0.0, 10.0, 0.0, 1.2, NPCA_WALK, false)
npc_heard(npc_st, 3, 12.0, 0.0, 10.0, 0.0, 1.2, NPCA_WALK, false)
for k in 0 .. 40 { npc_mirror_tick(npc_st, 0.05) }
expect_near(p.x, 12.0, 0.01)
expect(p.phase > 0.0)
npc_tick(npc_st, 0.05)
expect_near(p.x, 12.0, 0.01)
for k in 0 .. 140 { npc_mirror_tick(npc_st, 0.05) }
expect(not npc_heard_lately(npc_st, p))
}
}