From 338202c41b25040efa43756e8fd06ae9cfdabae3 Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Tue, 29 Sep 2026 13:33:54 +0300 Subject: [PATCH] play 2's frame keeps and a dropped file: a var() join made at once, the hint rail emptied in place, save_read gives its text back - ludic.ui cv_join (units_pieces.ludic:93): a var() value's pieces were joined by +, a text left behind per piece on every memo miss; it is written at its length at once (units_join.ludic), and the miss is its own declared function (cs_vars_miss), bounded by the memo. - ludic.hints hn_slots (rail.ludic:41): hints_reset made a new rail every time; the rail is emptied in place, made again only for another count (rail_slots.ludic). - value_spare_put (value.ludic:138): a list's spares grow only past the most it has ever cut off; declared as such. - ludic.save: save_read reads, parses and frees the file's text - the tree's strings are the parser's own - and says whether the text was whole (SaveRead.intact) for the words of a refusal. Maroon Lake's trip and home reads kept the whole file on every load and every menu card read. Tests: ludic.save (10), ludic.hints (8); the 36 ui examples; value_list_regrow, json_saves and alloc_fence_declared unchanged. Co-Authored-By: Claude Opus 5.5 --- packages/ludic.hints/index.ludic | 1 + packages/ludic.hints/rail.ludic | 8 +------- packages/ludic.hints/rail_slots.ludic | 15 +++++++++++++++ packages/ludic.save/format.ludic | 1 + packages/ludic.save/read.ludic | 12 +++++++++++- packages/ludic.ui/index.ludic | 1 + packages/ludic.ui/units.ludic | 5 +++++ packages/ludic.ui/units_join.ludic | 17 +++++++++++++++++ packages/ludic.ui/units_pieces.ludic | 5 ----- runtime/native/value.ludic | 1 + 10 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 packages/ludic.hints/rail_slots.ludic create mode 100644 packages/ludic.ui/units_join.ludic diff --git a/packages/ludic.hints/index.ludic b/packages/ludic.hints/index.ludic index 630237f6..08f6ed5c 100644 --- a/packages/ludic.hints/index.ludic +++ b/packages/ludic.hints/index.ludic @@ -6,6 +6,7 @@ import "ludic.base" import "defs.ludic" import "port.ludic" import "rail.ludic" +import "rail_slots.ludic" import "rail_into.ludic" import "card.ludic" import "mute.ludic" diff --git a/packages/ludic.hints/rail.ludic b/packages/ludic.hints/rail.ludic index c505b1d4..a2d98e0e 100644 --- a/packages/ludic.hints/rail.ludic +++ b/packages/ludic.hints/rail.ludic @@ -35,12 +35,6 @@ export function hints_config(hints_st: mut HintsState, shown: int, every: float) hints_st.hn_every = every hints_st.hn_top = hn_slots(shown) } -# a rail of n empty places -function hn_slots(n: int) -> []int { - let out = new []int - for i in 0 .. n { push(out, -1) } - return out -} # the next pass comes on the next tick rather than in a second export function hints_refresh(hints_st: mut HintsState) -> void { hints_st.hn_t = 0.0 } @@ -96,7 +90,7 @@ export function hints_pick(hints_st: HintsState) -> int { return hints_st.hn_pic export function hints_hot(hints_st: HintsState, k: int) -> bool { return k == hints_st.hn_pick } export function hints_reset(hints_st: mut HintsState) -> void { - hints_st.hn_top = hn_slots(hints_st.hn_shown) + hn_empty(hints_st) hints_st.hn_n = 0 hints_st.hn_t = 0.0 hints_st.hn_pick = 0 diff --git a/packages/ludic.hints/rail_slots.ludic b/packages/ludic.hints/rail_slots.ludic new file mode 100644 index 00000000..52059d29 --- /dev/null +++ b/packages/ludic.hints/rail_slots.ludic @@ -0,0 +1,15 @@ +# rail_slots.ludic - the rail's places: made for a count, emptied in place +# the rail emptied in place: made again only when it is to hold another count +function hn_empty(hints_st: mut HintsState) -> void { + if len(hints_st.hn_top) != hints_st.hn_shown { + hints_st.hn_top = hn_slots(hints_st.hn_shown) + return + } + for i in 0 .. len(hints_st.hn_top) { hints_st.hn_top[i] = -1 } +} +# a rail of n empty places +function hn_slots(n: int) -> []int { + let out = new []int + for i in 0 .. n { push(out, -1) } + return out +} diff --git a/packages/ludic.save/format.ludic b/packages/ludic.save/format.ludic index b86747d7..07e43b57 100644 --- a/packages/ludic.save/format.ludic +++ b/packages/ludic.save/format.ludic @@ -42,6 +42,7 @@ export property SaveRead { migrated: bool = false, readonly: bool = false, from_backup: bool = false, + intact: bool = true, # save_read: whether the text was whole (the words for a refusal) value: Val = null } diff --git a/packages/ludic.save/read.ludic b/packages/ludic.save/read.ludic index 242c2625..c125d452 100644 --- a/packages/ludic.save/read.ludic +++ b/packages/ludic.save/read.ludic @@ -13,7 +13,17 @@ export function save_parse(text: string, f: SaveFormat) -> SaveRead { return read_new(SAVE_OK, v) } -export function save_read(path: string, f: SaveFormat) -> SaveRead { return save_parse(Fs.read_text(path), f) } +# the file read and parsed, and its text given back - the tree's strings are the parser's own, and a +# load that kept the whole file for good kept it on every load and every menu card read +export function save_read(path: string, f: SaveFormat) -> SaveRead { + let text = Fs.read_text(path) + let r = save_parse(text, f) + if text != null { + r.intact = save_text_intact(text) + free(text) + } + return r +} # The version checks, then the chain. True when the object is now in today's shape; a newer file is # SAVE_FUTURE and read-only, an older one than the format carries is SAVE_CORRUPT. diff --git a/packages/ludic.ui/index.ludic b/packages/ludic.ui/index.ludic index bdd7153c..349a9322 100644 --- a/packages/ludic.ui/index.ludic +++ b/packages/ludic.ui/index.ludic @@ -16,6 +16,7 @@ import "memo_more.ludic" import "memo_join.ludic" import "eval_text.ludic" import "units_pieces.ludic" +import "units_join.ludic" import "expr.ludic" import "expr_parse.ludic" import "expr_prim.ludic" diff --git a/packages/ludic.ui/units.ludic b/packages/ludic.ui/units.ludic index 00e7d374..aafab67a 100644 --- a/packages/ludic.ui/units.ludic +++ b/packages/ludic.ui/units.ludic @@ -68,6 +68,11 @@ function cs_vars_in(ui_st: mut UiState, n: UiNode, v: Val) -> Val { if m.tags[i] == MM_VARS and cv_same(ui_st, m.keys[i]) { return m.vals[i] } at = (at + 1) & (MM_CAP - 1) } + return cs_vars_miss(ui_st, m) +} +# a value's var()s resolved to something not seen before: built once and kept in the memo +@alloc_ok("a memo miss: a var() value first seen, then kept, up to MM_CAP") +function cs_vars_miss(ui_st: mut UiState, m: UiMemo) -> Val { let raw = cv_join(ui_st) let r = Value.str(tpl_words(raw)) let j = mm_add(m, MM_VARS, raw) diff --git a/packages/ludic.ui/units_join.ludic b/packages/ludic.ui/units_join.ludic new file mode 100644 index 00000000..052b2294 --- /dev/null +++ b/packages/ludic.ui/units_join.ludic @@ -0,0 +1,17 @@ +# units_join.ludic - a var() value's pieces joined, for a memo miss (units.ludic) +# the pieces as one text, made at its length at once (joined by + it left a text per piece behind) +function cv_join(ui_st: UiState) -> string { + var n = 0 + for p in 0 .. len(ui_st.cv_texts) { n += len(ui_st.cv_texts[p]) } + var out: pointer = null + unsafe { out = bytes(n + 1) } + var at = 0 + for p in 0 .. len(ui_st.cv_texts) { + let t = ui_st.cv_texts[p] + unsafe { for i in 0 .. len(t) { out[at + i] = t[i] } } + at += len(t) + } + unsafe { out[n] = 0 } + let s: string = out + return s +} diff --git a/packages/ludic.ui/units_pieces.ludic b/packages/ludic.ui/units_pieces.ludic index bd53b88c..f6a115dd 100644 --- a/packages/ludic.ui/units_pieces.ludic +++ b/packages/ludic.ui/units_pieces.ludic @@ -88,8 +88,3 @@ function cv_same(ui_st: UiState, k: string) -> bool { } return at == len(k) } -function cv_join(ui_st: UiState) -> string { - var out = "" - for p in 0 .. len(ui_st.cv_texts) { out = out + ui_st.cv_texts[p] } - return out -} diff --git a/runtime/native/value.ludic b/runtime/native/value.ludic index 990150f3..a697bf9d 100644 --- a/runtime/native/value.ludic +++ b/runtime/native/value.ludic @@ -133,6 +133,7 @@ function value_item(l: Val, i: int, tag: int) -> Val { push(l.kids, v) return v } +@alloc_ok("a list's spares: grown only past the most it has ever cut off, then kept") function value_spare_put(l: Val, v: Val) -> void { if l.spare == null { l.spare = new []Val } push(l.spare, v)