From 8fbfeb7f9d52fec813b513576826f250f42d8765 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Mon, 28 Sep 2026 17:06:50 +0300 Subject: [PATCH] ludic.base: core_undrained fills a list the caller keeps; hd_refuse's panic declared; ludic.photo's slug declared (a photograph taken) Co-Authored-By: Claude Opus 5.5 --- packages/ludic.base/README.md | 2 +- packages/ludic.base/handle.ludic | 1 + packages/ludic.base/queue.ludic | 12 ++++++------ packages/ludic.base/tests/queue_test.ludic | 6 +++--- packages/ludic.base/tests/route_test.ludic | 2 +- packages/ludic.photo/slug.ludic | 1 + 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/ludic.base/README.md b/packages/ludic.base/README.md index 4b98a415..46526756 100644 --- a/packages/ludic.base/README.md +++ b/packages/ludic.base/README.md @@ -40,7 +40,7 @@ import "ludic.base" | `Tick { dt, frame, hours }`, `tick_new(dt, frame, hours)` | what a system's tick receives | | `PH_INPUT`, `PH_SENSE`, `PH_SIMULATE`, `PH_RESOLVE`, `PH_COMMIT`, `PH_PRESENT`, `PH_COUNT` | the phases, in the order they run | | `Queue`, `queue_new(name)`, `q_push(q, v)`, `q_drain(q) -> []T`, `q_len(q)`, `q_clear(q)` | facts, first in first out (`queue_new`, not `q_new`: that name is render3d's quaternion). A queue keeps its own count, so its verbs take only the queue: a mechanic's verbs take only the mechanic's own state | -| `q_tag(q) -> QueueTag`, `core_undrained(tags) -> []string` | the names of the given queues still holding facts; ask it at the end of a frame | +| `q_tag(q) -> QueueTag`, `core_undrained(tags, out) -> int` | the names of the given queues still holding facts, into a list the caller keeps; ask it at the end of a frame | | `Rng`, `rng_new(seed)`, `rng_seed(r, seed)`, `rng_next(r)`, `rng_float(r)`, `rng_between(r, lo, hi)`, `rng_span(r, lo, hi)` | a xorshift32 stream of its own (`rng_between` is inclusive; `rng_range` is taken by `Random.range`) | | `SaveNode { found, version, data }`, `save_tree()`, `save_section(root, key, version, v)`, `load_section(root, key) -> SaveNode`, `save_encode`, `save_decode` | the save tree: `{"fishing": {"v": 3, ...}}`; `v` is reserved in a section, and a non-object value rides under `data` | | `sv_int`, `sv_float`, `sv_bool`, `sv_str` (key, fallback), `sv_ints` | typed reads with a fallback; a float is kept as thousandths | diff --git a/packages/ludic.base/handle.ludic b/packages/ludic.base/handle.ludic index ea8435e7..0195e417 100644 --- a/packages/ludic.base/handle.ludic +++ b/packages/ludic.base/handle.ludic @@ -34,6 +34,7 @@ export function hd_new(p: HandlePool) -> int { return s | (p.gen[s] << 22) } +@alloc_ok("only when a pool is full: the words of its panic") function hd_refuse(p: HandlePool) -> int { p.refused += 1 if p.strict { panic(`handle pool {p.name} is full at {len(p.gen)}`) } diff --git a/packages/ludic.base/queue.ludic b/packages/ludic.base/queue.ludic index fbee553e..f0ba09e9 100644 --- a/packages/ludic.base/queue.ludic +++ b/packages/ludic.base/queue.ludic @@ -73,12 +73,12 @@ export function q_unheld(q: Queue, pool: []T, out: []T) -> int { # a queue's tag, for core_undrained: queues of different facts in one list export function q_tag(q: Queue) -> QueueTag { return q.tag } -# the names of the given queues still holding facts; ask it at the end of a frame, where a fact -# left behind is a route nobody wrote -export function core_undrained(tags: []QueueTag) -> []string { - let out = new []string +# the names of the given queues still holding facts, into `out` (cleared first) and counted; ask it +# at the end of a frame, where a fact left behind is a route nobody wrote +export function core_undrained(tags: []QueueTag, out: []string) -> int { + List.clear(out) for i in 0 .. len(tags) { - if tags[i] != null and tags[i].pending > 0 { push(out, tags[i].name) } + if tags[i] != null and tags[i].pending > 0 { kept_push(out, tags[i].name) } } - return out + return len(out) } diff --git a/packages/ludic.base/tests/queue_test.ludic b/packages/ludic.base/tests/queue_test.ludic index 37f54892..224b832a 100644 --- a/packages/ludic.base/tests/queue_test.ludic +++ b/packages/ludic.base/tests/queue_test.ludic @@ -86,11 +86,11 @@ program QueueTest { q_push(b, 2) q_drain(b) let tags = [q_tag(a), q_tag(b)] - let names = core_undrained(tags) - expect_eq(len(names), 1) + let names = new []string + expect_eq(core_undrained(tags, names), 1) expect(names[0] == "left_behind") q_drain(a) - expect_eq(len(core_undrained(tags)), 0) + expect_eq(core_undrained(tags, names), 0) } # an empty drain allocates nothing and hands back the same empty list; a drained list is the diff --git a/packages/ludic.base/tests/route_test.ludic b/packages/ludic.base/tests/route_test.ludic index 7e696292..0c3aa8c9 100644 --- a/packages/ludic.base/tests/route_test.ludic +++ b/packages/ludic.base/tests/route_test.ludic @@ -35,7 +35,7 @@ program RouteTest { expect(not fishing_cast(toy_fishing_st, 500.0, 2.0)) core_tick_all(base_st, tick_new(1.0 / 60.0, 0, 0.0)) expect_eq(pack_total(toy_pack_st), 3) - expect_eq(len(core_undrained([q_tag(toy_fishing_st.caught)])), 0) + expect_eq(core_undrained([q_tag(toy_fishing_st.caught)], new []string), 0) } test "the pack saves its own section and the fishing, which saves nothing, is reset" (base_st: mut BaseState, toy_fishing_st: mut ToyFishingState, toy_pack_st: ToyPackState) { diff --git a/packages/ludic.photo/slug.ludic b/packages/ludic.photo/slug.ludic index 2c648871..8d9aa98c 100644 --- a/packages/ludic.photo/slug.ludic +++ b/packages/ludic.photo/slug.ludic @@ -2,6 +2,7 @@ # at most 24 characters, so a folder of photographs browses well const PHT_ALPHA: string = "0123456789abcdefghijklmnopqrstuvwxyz" +@alloc_ok("on a photograph taken: its file name") export function photo_slug(tags: string) -> string { var out = "" var n = 0