feat(engine): #90 atlas-aware Sprite component, #91 become from listeners, 0.3.x ergonomics batch
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 32s
ci / build-and-test (push) Successful in 2m49s
commit-lint / conventional-commits (push) Successful in 5s
docs / build-and-deploy (push) Successful in 30s

Closes the two open issues and lands the pending unreleased batch:

- #90: `Sprite { atlas: 1 }` routes esys_sprite through atlas_draw_ex
  (scale/flip/tint), so cell / cell_span / strip ids of any size draw
  through the engine sprite-render system. examples/library/sprite_atlas
  is the pixel-readback regression.
- #91: `become` from an @On(Event) listener / global handler / plain
  function no longer segfaults the compiler; it emits @L_scene_leave()
  (a dispatch on the live scene id) so the leaving scene's on-exit runs.
  UI_* handles are readable from any code (widget table built on first
  use). examples/library/scene_menus covers it.
- fix: a windowed `ludicc -o` build that reaches the audio runtime only
  through the atlas/Assets preload import now links audio.ll +
  AVFoundation (the audio backend link was gated on a game-level
  Audio.* call, so any windowed game declaring Sprite failed to link).
- the hand-written "Unreleased" CHANGELOG section is converted to
  changesets under changes/ so `x release` generates it.
- plus the batch: engine-driven retained UI + UiClicked event, Overlay
  phase, TileSkin tilemap-render system, Key.* constants, Font/Ui/File
  namespaces, Sprite.strip, prefabs, managers, countdown fields,
  enum-typed machines, layer @Queries, ludic.prefs / ludic.dungeon
  packages, Ai.seek pathing, Solids.solid2, cursor confine (mode 3)
  fix, shooter centre-aim fix, reserved-word function diagnostic.

Verified: x test (124/124), x test-tools, check-impl, check-vocabulary,
check-docs, docs-gen + docs-check, bootstrap-cfree (seed is a fixpoint).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-04 01:36:08 +03:00
parent e9c2c51bc3
commit ad548840c7
139 changed files with 58981 additions and 43671 deletions

View file

@ -19,6 +19,29 @@ function ui_prop(w: Node, key: pointer) -> Node {
while i < len(w.b.kids) { if (w.b.kids[i].s == key) { return w.b.kids[i].a }; i = i + 1 }
return null
}
# a widget's props plus the text props (font, size, fg, align) it inherits from the
# nearest ancestor that sets them — so a panel states the menu's look once.
function ui_props_with_inherited(i: int) -> []Node {
let w = uiw[i]
let out = new []Node
var p = 0
while p < len(w.b.kids) { push(out, w.b.kids[p]); p = p + 1 }
let keys = new []pointer
push(keys, "font"); push(keys, "size"); push(keys, "fg"); push(keys, "align")
var k = 0
while k < len(keys) {
if ui_prop(w, keys[k]) == null {
var a = uiw_parent[i]
while a >= 0 {
let v = ui_prop(uiw[a], keys[k])
if (v != null) { let fi = node(E_FINIT); fi.s = keys[k]; fi.a = v; push(out, fi); a = 0 - 1 }
else { a = uiw_parent[a] }
}
}
k = k + 1
}
return out
}
function ui_flatten(w: Node, parent: int) -> void {
let idx = len(uiw)
push(uiw, w); push(uiw_parent, parent)
@ -41,6 +64,7 @@ function has_ui() -> bool {
# index of a UI_<name>: a ui block's root, or a widget's id=
function ui_index_of(nm: pointer) -> int {
if uiw == null { ui_flatten_all() } # a UI_ id may be read before @ui_build is emitted (a function, a listener)
let s = nm[3..len(nm)] # strip "UI_"
let u = 0; var bi = 0
var i = 0
@ -86,12 +110,13 @@ function emit_ui_build() -> void {
ll_ui_set(i, 0, itoa(t))
ll_ui_set(i, 1, itoa(uiw_parent[i]))
if t == 4 { ll_ui_set(i, 17, "1") }
let props = ui_props_with_inherited(i)
var p = 0
while p < len(w.b.kids) {
let pr = w.b.kids[p]
while p < len(props) {
let pr = props[p]
let k = pr.s
let v = pr.a
if (k == "id") { p = p + 1; continue }
if (k == "id") or (k == "goto") { p = p + 1; continue }
if (k == "text") {
let sv = emit_expr(v)
emit(" call void @fn_rt_ui_static_text(i32 "); emit(itoa(i)); emit(", ptr "); emit(sv.code); emit(")\n")