ludic.things: a Thing's dawn (the last day a restock reached it, saved); things_restock takes the day; things_catch_up restocks a chunk's Things that missed a dawn once a morning would reach them, as that morning would have

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 21:14:42 +03:00
parent 0634116d59
commit b053bf625c
7 changed files with 57 additions and 8 deletions

View file

@ -22,6 +22,7 @@ property Thing {
timer, used, n # state the kind decides the meaning of
sx, sz, syaw # a heading or a point the kind decides the meaning of
seat, seats, taken # its seats: the first of the game's seat rows, how many, a bit per one sat in
dawn # the last day a restock reached it (saved), for the catch-up
shared # the game copies it to others (a party)
}
```
@ -89,7 +90,8 @@ the actor the moment the Thing exists (a caller often scales or tints it on the
| `things_all()`, `things_count()`, `things_ready()`, `thing_by_uid(uid)` | every Thing, in the order placed; whether a world was ever set out |
| `thing_find(kind, id)`, `thing_nearest(kind, x, z)`, `things_within(x, z, r)`, `things_each(kind)`, `things_each_all(kind)`, `thing_dist2(t, x, z)` | where they are: only active ones answer except the `_all`; a kind below 0 is any; `within` is nearest first |
| `thing_name(t)`, `thing_prompt(t)`, `thing_use(t)`, `thing_can_use(t)`, `thing_reach(t)`, `thing_kind(t)`, `thing_is_personal(t)`, `thing_is_put_down(t)` | the dispatcher |
| `things_tick(hours)`, `things_restock()` | every active kind that ticks; every Thing the world grew back and unused |
| `things_tick(hours)`, `things_restock(day)` | every active kind that ticks; every Thing the world grew back and unused where a morning reaches (`ThingsWorld.restocks`), its `dawn` set to the day |
| `things_catch_up(chunk, day, out)` | a chunk's Things that missed a dawn (`dawn < day`) and a morning now reaches, restocked as that morning would have (no dice), into the caller's list |
| `things_facts() -> Queue<ThingFact>` | `{ what, uid, kind, id, owner, x, z }`: `THING_PLACED`, `THING_REMOVED`, `THING_USED` |
| `thing_kind_ok(k)`, `thing_kind_by_key(key)` | the kinds |
| `things_system() -> System` | `"things"`, `PH_COMMIT`: its save section - every active Thing the port keeps, its kind by KEY, so the game may reorder its kinds - and a reset that removes the kept ones for the load to put back |

View file

@ -16,6 +16,20 @@ export function things_drop_chunk(things_st: mut ThingsState, chunk: int) -> int
return n
}
# the chunk's Things that missed a dawn and a morning would now reach, restocked as that morning
# would have (no dice: a restock rolls none) and filed into the caller's list; how many
export function things_catch_up(things_st: ThingsState, chunk: int, day: int, out: []Thing) -> int {
List.clear(out)
let rows = ix_rows(things_st.th.chunks, chunk)
for i in 0 .. len(rows) {
let t = things_st.th.tab.rec[rows[i]]
if t.dawn >= day or thing_is_put_down(t) or not ThingsWorld.restocks(t.x, t.z) { continue }
th_restock_one(t, day)
kept_push(out, t)
}
return len(out)
}
# change detection: the tick now, the table moving on to the next - a consumer keeps what this
# returned and asks things_changed(since: it) for what was written after
export function things_now(things_st: mut ThingsState) -> int {

View file

@ -64,14 +64,18 @@ export function things_tick(things_st: mut ThingsState, hours: float) -> void {
}
}
# a restock: every Thing the world grew is back, and loses its `used` unless its kind keeps it;
# what was put down is left as it is
export function things_restock(things_st: ThingsState) -> void {
# a restock on `day`: every Thing the world grew where a morning reaches is back, and loses its
# `used` unless its kind keeps it; what was put down is left as it is
export function things_restock(things_st: ThingsState, day: int) -> void {
let all = things_all(things_st)
for i in 0 .. len(all) {
let t = all[i]
if thing_is_put_down(t) or not ThingsWorld.restocks(t.x, t.z) { continue }
th_restock_one(t, day)
}
}
function th_restock_one(t: Thing, day: int) -> void {
if not ThingKinds[t.kind].keeps { t.used = 0 }
if not t.active { thing_show(t) }
}
t.dawn = day
}

View file

@ -21,6 +21,7 @@ export property Thing {
seat: int = -1 # the first of its model's seat rows in the game's table; -1 none
seats: int = 0 # how many seat rows follow it
taken: int = 0 # a bit per seat someone is sitting in; never saved
dawn: int = 0 # the last day a restock reached it (a morning, or a catch-up)
shared: bool = false # the game copies it to others (a party)
ent: int = -1 # its handle in the table, -1 once removed
tab: Table<Thing> = null # the table holding it, so a verb on the Thing alone keeps the indexes

View file

@ -50,6 +50,7 @@ function th_clean(t: Thing) -> Thing {
t.seat = -1
t.seats = 0
t.taken = 0
t.dawn = 0
t.shared = false
t.ent = -1
t.tab = null

View file

@ -31,6 +31,7 @@ function th_to_json(t: Thing) -> Val {
sv_put_float(o, "syaw", t.syaw)
sv_put_int(o, "seat", t.seat)
sv_put_int(o, "seats", t.seats)
sv_put_int(o, "dawn", t.dawn)
return o
}
@ -61,6 +62,7 @@ function th_from_json(things_st: mut ThingsState, o: Val) -> void {
t.syaw = sv_float(o, "syaw", 0.0)
t.seat = sv_int(o, "seat", -1)
t.seats = sv_int(o, "seats", 0)
t.dawn = sv_int(o, "dawn", 0)
let x = sv_float(o, "x", 0.0)
let z = sv_float(o, "z", 0.0)
let y = sv_float(o, "y", ThingsWorld.ground(x, z))

View file

@ -10,6 +10,7 @@ program ThingsTest {
shows: int = 0
back: int = 0
wood: int = 0
edge: float = 1000000.0 # a morning reaches x below this (the game's zones)
}
function lit_prompt(t: Thing) -> string {
@ -42,7 +43,8 @@ program ThingsTest {
function on_removed(things_test_st: mut ThingsTestState, t: Thing) -> void { things_test_st.gone += 1 }
function on_shown(things_test_st: mut ThingsTestState, t: Thing) -> void { things_test_st.shows += 1 }
function on_restored(things_test_st: mut ThingsTestState, t: Thing) -> void { things_test_st.back += 1 }
bind ThingsWorld { ground: fn hill, placed: fn on_placed, removed: fn on_removed, shown: fn on_shown, restored: fn on_restored }
function in_reach(things_test_st: ThingsTestState, x: float, z: float) -> bool { return x < things_test_st.edge }
bind ThingsWorld { restocks: fn in_reach, ground: fn hill, placed: fn on_placed, removed: fn on_removed, shown: fn on_shown, restored: fn on_restored }
function put(things_st: mut ThingsState, kind: int, x: float, z: float) -> Thing { return thing_spawn(things_st, kind, "m", x, 0.0, z, 0.0, 0) }
function facts(things_st: ThingsState) -> []ThingFact { return q_drain(things_facts(things_st)) }
@ -129,7 +131,7 @@ program ThingsTest {
expect(l.active)
things_tick(things_st, 0.6)
expect(not l.active)
things_restock(things_st)
things_restock(things_st, 1)
expect(b.active)
expect(not l.active)
expect_eq(f.used, 1)
@ -292,4 +294,27 @@ program ThingsTest {
expect_eq(t.seats, 2)
expect_eq(t.taken, 0)
}
test "a Thing a morning missed is restocked once a morning would reach it, and only once" (things_st: mut ThingsState, things_test_st: mut ThingsTestState) {
things_clear(things_st)
let near = put(things_st, TH_BRANCH, 0.0, 0.0)
let far = put(things_st, TH_BRANCH, 500.0, 0.0)
thing_use(things_st, near)
thing_use(things_st, far)
things_test_st.edge = 100.0
things_restock(things_st, 3)
expect(near.active)
expect_eq(near.dawn, 3)
expect(not far.active)
let out = new []Thing
expect_eq(things_catch_up(things_st, things_chunk(500.0, 0.0), 3, out), 0)
expect(not far.active)
things_test_st.edge = 1000000.0
expect_eq(things_catch_up(things_st, things_chunk(500.0, 0.0), 3, out), 1)
expect(far.active)
expect_eq(far.dawn, 3)
expect(out[0] == far)
expect_eq(things_catch_up(things_st, things_chunk(500.0, 0.0), 3, out), 0)
expect_eq(things_catch_up(things_st, things_chunk(0.0, 0.0), 3, out), 0)
}
}