feat(packages): ludic.jobs - written jobs gated by standing and story, boards of posts rolled by the day on their own dice, progress counted from events and read back from state, handing in, rewards through a port, facts, a save section per scope

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 07:35:45 +03:00
parent e146c7a337
commit 411206e45a
14 changed files with 984 additions and 0 deletions

View file

@ -0,0 +1,89 @@
# boards.ludic - posting: the morning takes down what is stale and asks the game for a new one, on
# the jobs' own dice seeded by the day, so a board never moves anyone else's rolls
export function jobs_board_ok(b: int) -> bool { return b >= 0 and b < BOARD_COUNT }
function jobs__at(b: int, s: int) -> int {
jobs__ensure()
return jobs__off[b] + s
}
# a whole number in [lo, hi] from the jobs' dice: what JobsWorld.roll draws with
export function jobs_roll(lo: int, hi: int) -> int {
jobs__ensure()
return rng_between(jobs__dice, lo, hi)
}
# inside JobsWorld.roll: what goes up in the slot being filled
export function jobs_post(b: int, s: int, kind: int, param: int, need: int, money: int, rep: int) -> void {
let k = jobs__at(b, s)
jobs__pk[k] = kind
jobs__pp[k] = param
jobs__pn[k] = need
jobs__pm[k] = money
jobs__pr[k] = rep
jobs__ph[k] = 0
jobs__ps[k] = JOBS_ACTIVE
jobs__pd[k] = JobsWorld.day()
let e = JobsWorld.evidence(kind, param)
jobs__pb[k] = 0
if e > 0 { jobs__pb[k] = e }
}
function jobs__clear(k: int) -> void {
jobs__pk[k] = -1
jobs__pp[k] = -1
jobs__pn[k] = 0
jobs__ph[k] = 0
jobs__ps[k] = JOBS_OFFERED
}
# take a slot down and ask the game for what replaces it
function jobs__repost(b: int, s: int) -> void {
let k = jobs__at(b, s)
let day = JobsWorld.day()
rng_seed(jobs__dice, jobs__seed + day * 7 + b * 7919 + s * 104729 + jobs__serial[b] * 31)
jobs__serial[b] += 1
jobs__clear(k)
JobsWorld.roll(b, s)
if jobs__pk[k] >= 0 { jobs__fact(JOBS_F_OFFERED, -1, b, s, 0, jobs__pn[k]) }
}
# a new day, on the boards this machine keeps: an empty or stale post is replaced, and one
# finished on an earlier day
export function jobs_morning() -> void {
jobs__ensure()
let day = JobsWorld.day()
for b in 0 .. BOARD_COUNT {
let bd = JobBoards[b]
if not JobsWorld.owns(bd.scope) { continue }
for s in 0 .. bd.slots {
let k = jobs__off[b] + s
let finished = jobs__ps[k] == JOBS_DONE and day > jobs__pd[k]
if jobs__pk[k] < 0 or finished or day - jobs__pd[k] >= bd.keep { jobs__repost(b, s) }
}
}
}
function jobs__post_moved(b: int, s: int, have: int) -> void {
let k = jobs__off[b] + s
let was = jobs__ph[k]
jobs__ph[k] = have
if jobs__ph[k] > jobs__pn[k] { jobs__ph[k] = jobs__pn[k] }
if jobs__ph[k] > was { jobs__fact(JOBS_F_PROGRESSED, -1, b, s, jobs__ph[k], jobs__pn[k]) }
if jobs__ps[k] != JOBS_ACTIVE or jobs__ph[k] < jobs__pn[k] { return }
jobs__ps[k] = JOBS_READY
jobs__fact(JOBS_F_COMPLETED, -1, b, s, jobs__ph[k], jobs__pn[k])
if JobBoards[b].auto { jobs_post_hand_in(b, s) }
}
export function jobs_post_hand_in(b: int, s: int) -> bool {
if not jobs_board_ok(b) or s < 0 or s >= JobBoards[b].slots { return false }
let k = jobs__at(b, s)
if jobs__ps[k] != JOBS_READY { return false }
jobs__ps[k] = JOBS_DONE
jobs__total[b] += 1
JobsPay.reward(JobBoards[b].scope, jobs__pm[k], jobs__pr[k])
jobs__fact(JOBS_F_HANDED_IN, -1, b, s, jobs__ph[k], jobs__pn[k])
if JobBoards[b].refill { jobs__repost(b, s) }
return true
}