frame keeps: a model's records filled in place, kept event numbers, list spares, Json.read_file

From the fence's kept frames in 22 minutes of play:

- A component field that is a record or a list of records was `value_put(o, k, view_val_T(x))`,
  a whole new tree every frame (HudPrompt's notifications). view_fill.ludic generates view_set_T /
  view_set_list_T / view_fill_T that fill the object and list under the key in place.
- value_list_fit dropped the items it cut off and value_item made new ones as the list grew back,
  a Value per item per regrowth (value_item / value_set_strs); the cut-off items are now the
  list's spares (Val.spare), and an item of another kind is turned rather than replaced.
- ludic.ui: a scroll box's "scroll" and a slider's "change" fired a fresh Value.float a frame
  (sc_walk, scroll.ludic:36); ui_fire_float takes one from a ring kept with the state (fired.ludic).
  ui_object_fit_into is exported, for a draw that keeps its list.
- Json.read_file(path): read, parsed, and the file's text given back - Json.parse(Fs.read_text())
  kept the whole file on every read (Maroon Lake's settings peeks).

Golden value_list_regrow: a list alternating 6 and 2 items every frame keeps nothing (the toolchain
before this fails it: +128 B new Val from value_item). Game compiles; ludic.i18n/settings/hints/
base tests pass (ludic.ui has none); arena and fence goldens unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-29 12:22:07 +03:00
parent 9c24d707bc
commit a299117858
15 changed files with 71088 additions and 69253 deletions

View file

@ -51,6 +51,8 @@ export state UiState {
ev_ring_at: int = 0
nt_evs: []Val = nt_evs_new() # native.ludic: fired events' objects, NT_EVS of them, made with the state
nt_ev_at: int = 0
nt_nums: []Val = nt_nums_new() # fired.ludic: the numbers a scroll or a slider reports, a ring
nt_num_at: int = 0
hd_ev: Val = hd_ev_new() # hold.ludic: the held control's event, filled in place
cv_texts: []string = new []string # units_pieces.ludic: a value's var() pieces, kept
tx_pieces: [][]string = new [][]string # eval_text.ludic: a text's pieces, a list a depth

View file

@ -46,7 +46,7 @@ function ct_range_set(ui_st: mut UiState, n: UiNode, v0: float) -> void {
let lo = Value.as_float(ui_attr(n, "min"))
let hi = Value.as_float(ui_attr(n, "max"))
let v = Math.clamp(v0, lo, Math.max(hi, lo))
if v != Value.as_float(ui_attr(n, "value")) { ui_fire(ui_st, n, "change", Value.float(v)) }
if v != Value.as_float(ui_attr(n, "value")) { ui_fire_float(ui_st, n, "change", v) }
}
# a range's track pressed or dragged: the value where the pointer is
function ct_press(ui_st: mut UiState, n: UiNode, x: float, y: float) -> void {

View file

@ -0,0 +1,16 @@
# fired.ludic - the number a control reports on each frame it moves (a box scrolled, a slider dragged),
# from a ring of Values kept with the state: a fresh Value.float a frame was kept by nothing. The
# queue runs within the frame, long before NT_NUMS more come round to one
const NT_NUMS: int = 64
function nt_nums_new() -> []Val {
let l = new []Val
for i in 0 .. NT_NUMS { push(l, Value.float(0.0)) }
return l
}
export function ui_fire_float(ui_st: mut UiState, n: UiNode, name: string, x: float) -> void {
let at = ui_st.nt_num_at % NT_NUMS
ui_st.nt_num_at = at + 1
let v = ui_st.nt_nums[at]
Value.be_float(v, x)
ui_fire(ui_st, n, name, v)
}

View file

@ -24,6 +24,7 @@ import "env.ludic"
import "eval.ludic"
import "act.ludic"
import "native.ludic"
import "fired.ludic"
import "native_room.ludic"
import "controls.ludic"
import "controls_build.ludic"

View file

@ -13,7 +13,8 @@ function ly_ratio(n: UiNode, fixed_w: float, avail: float) -> void {
}
# x, y, w, h: a `nat_w` x `nat_h` picture in the node's content box by its object-fit
export function ui_object_fit(n: UiNode, nat_w: float, nat_h: float) -> []float { return ui_object_fit_into(new []float, n, nat_w, nat_h) }
function ui_object_fit_into(out: []float, n: UiNode, nat_w: float, nat_h: float) -> []float {
# ... into the caller's kept list (a draw every frame should: ui_object_fit makes one a call)
export function ui_object_fit_into(out: []float, n: UiNode, nat_w: float, nat_h: float) -> []float {
let bx = n.x + n.pl + n.bl
let by = n.y + n.pt + n.bt
let bw = n.cw - ly_hin(n)

View file

@ -32,7 +32,7 @@ function sc_walk(ui_st: mut UiState, n: UiNode, live: bool) -> void {
var off = held
if live { off = sc_input(ui_st, n, off, room, most) }
off = Math.clamp(off, 0.0, most)
if live and off != Math.clamp(held, 0.0, most) { ui_fire(ui_st, n, "scroll", Value.float(off / ui_px(ui_st, 1.0))) }
if live and off != Math.clamp(held, 0.0, most) { ui_fire_float(ui_st, n, "scroll", off / ui_px(ui_st, 1.0)) }
sc_put(ui_st, n.key, off)
n.scroll_y = off
if off != 0.0 {