feat(ludic.steps): chapters of steps, their kinds, progress and a party's pooled shares

A step is (kind, param, need) and a question about state: a kind has a
shape (yes, a count, a mask counted, every bit of a mask) and a pool
(world, any, sum, or, max, each), handed in by the game as StepsKind
records. An arc is chapters of up to STEPS_PER_CHAPTER steps; steps_check
sticks each met step of the current chapter (STEPS_MET) and opens the
next when all are (STEPS_CHAPTER), so a step met before its chapter
ticks at once. With company each seat's shares are held per chapter,
pooled as the kind says (steps_met, steps_party_got), counted for an
EACH step (steps_each, steps_lacking), and a leaver takes theirs along.

Port: StepsWorld { value(kind, param, player) (required), party,
present }. The package knows no kind's meaning and no chapter's words.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 06:02:39 +03:00
parent 4318adc98c
commit 31e6740033
10 changed files with 719 additions and 0 deletions

View file

@ -0,0 +1,92 @@
# party.ludic - with company every player reports what THEY have toward each step of the current
# chapter (steps_mine) and the owner of the story answers the step from the lot. Held per seat,
# so a player who leaves takes their column with them; seat 0 is this machine, read live.
var steps__players: int = 12
var steps__col: []int = null # players * STEPS_PER_CHAPTER shares
var steps__col_ch: int = -1 # the chapter they are about
# how many seats a party has
export function steps_config(players: int) -> void {
steps__players = players
steps__col = null
}
function steps__party_clear() -> void {
steps__col = new []int
for i in 0 .. steps__players * STEPS_PER_CHAPTER { push(steps__col, 0) }
steps__col_ch = steps__cur
}
# a party over (or a new one): every column forgotten
export function steps_party_reset() -> void { steps__col = null }
# the columns, emptied when the chapter they were about is not this one; seat 0 refilled
function steps__cols() -> void {
if steps__col == null or steps__col_ch != steps__cur { steps__party_clear() }
if steps_finished() { return }
for i in 0 .. steps_in(steps__cur) {
steps__col[i] = steps_mine(steps_kind(steps__cur, i), steps_param(steps__cur, i), steps_need(steps__cur, i))
}
}
# this machine's shares for the current chapter, to send to whoever owns the story
export function steps_my_shares() -> []int {
steps__cols()
let out = new []int
for i in 0 .. STEPS_PER_CHAPTER { push(out, steps__col[i]) }
return out
}
# a teammate's shares, as they sent them; ignored when they were about another chapter
export function steps_share(p: int, ch: int, shares: []int) -> void {
if p <= 0 or p >= steps__players or ch != steps__cur { return }
steps__cols()
for i in 0 .. min(len(shares), STEPS_PER_CHAPTER) { steps__col[p * STEPS_PER_CHAPTER + i] = shares[i] }
}
export function steps_share_of(p: int, i: int) -> int {
steps__cols()
return steps__col[p * STEPS_PER_CHAPTER + i]
}
# a seat emptied: what it had goes with it
export function steps_leave(p: int) -> void {
if steps__col == null or p <= 0 or p >= steps__players { return }
for i in 0 .. STEPS_PER_CHAPTER { steps__col[p * STEPS_PER_CHAPTER + i] = 0 }
}
# the shares of every seat taken, combined as the pool says (seat 0 is the start)
function steps__combined(i: int, how: int) -> int {
var v = steps__col[i]
for p in 1 .. steps__players {
if not StepsWorld.present(p) { continue }
let c = steps__col[p * STEPS_PER_CHAPTER + i]
if how == STEPS_POOL_ANY or how == STEPS_POOL_OR { v = v | c }
else if how == STEPS_POOL_SUM { v += c }
else if how == STEPS_POOL_MAX { if c > v { v = c } }
}
return v
}
# the answer to step i of the current chapter: solo this is steps_eval and nothing more
export function steps_met(i: int) -> bool {
let ch = steps__cur
let kind = steps_kind(ch, i)
if not StepsWorld.party() { return steps_eval(kind, steps_param(ch, i), steps_need(ch, i)) }
let how = steps_pool(kind)
if how == STEPS_POOL_WORLD { return steps_eval(kind, steps_param(ch, i), steps_need(ch, i)) }
steps__cols()
return steps_from(kind, steps_param(ch, i), steps_need(ch, i), steps__combined(i, how), steps_players(), steps_each(i))
}
# the party's count for step i of the current chapter, -1 for one that is not a tally
export function steps_party_got(i: int) -> int {
let ch = steps__cur
let kind = steps_kind(ch, i)
let param = steps_param(ch, i)
if steps_got(kind, param) < 0 { return -1 }
let how = steps_pool(kind)
if not StepsWorld.party() or how == STEPS_POOL_WORLD or how == STEPS_POOL_ANY or how == STEPS_POOL_EACH { return steps_got(kind, param) }
steps__cols()
return steps_got_from(kind, param, steps__combined(i, how))
}