ludic/examples/library/sprite_atlas.ludic
Orkuncakilkaya ad548840c7
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
feat(engine): #90 atlas-aware Sprite component, #91 become from listeners, 0.3.x ergonomics batch
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>
2026-09-04 01:36:08 +03:00

51 lines
2.3 KiB
Text

# sprite_atlas.ludic — the engine sprite-render system draws atlas ids (#90).
# `Sprite { id, atlas: 1 }` marks `id` as a Sprite.cell / Sprite.cell_span sprite
# (any size, here a 16x32 two-cell character) and esys_sprite routes it through
# atlas_draw_ex with scale / flip / tint, so the #81 atlas API and the #85 engine
# sprite system compose. atlas: 0 (the default) keeps `id` a 16x16 table sprite.
# Verified by pixel readback after one frame.
#
# Driven headless by one stdin key ('q' quits after the frame renders).
# printf 'q' | LUDIC_MODULES=packages bin/ludic examples/library/sprite_atlas.ludic -> 32 1 1 1 0
program SpriteAtlas {
import "ludic.core/components.ludic"
var TALL: int = 0
function bi(b: bool) -> int { if b { return 1 }; return 0 }
@OnStart handler Boot {
let sheet = Sprite.sheet("assets/kenney/tiny-dungeon/Tilemap/tilemap_packed.png", 16, 16)
TALL = Sprite.cell_span(sheet, 0, 0, 1, 2) # a 16x32 sprite: two cells tall
spawn Tall { Position { x: 40, y: 40 }, Sprite { id: TALL, atlas: 1 } }
spawn Tinted { Position { x: 100, y: 40 }, Sprite { id: TALL, atlas: 1, tint: 0xff0000 } }
spawn Table { Position { x: 160, y: 40 }, Sprite { id: TALL } } # atlas: 0 -> the (empty) 16x16 table
}
# An empty Render handler: the engine sprite system draws the Sprite entities.
handler Draw phase Render { }
@OnQuit handler Report {
print(Sprite.height(TALL)) # 32 — the span is two cells tall
var top = 0
for yy in 40 .. 56 { for xx in 40 .. 56 { if Screen.pixel(xx, yy) != 0 { top = 1 } } }
print(top) # 1 — the upper cell drew
var bottom = 0
for yy in 56 .. 72 { for xx in 40 .. 56 { if Screen.pixel(xx, yy) != 0 { bottom = 1 } } }
print(bottom) # 1 — the lower cell drew too (a table sprite stops at 16px)
var tinted = 1
var seen = 0
for yy in 40 .. 72 { for xx in 100 .. 116 {
let c = Screen.pixel(xx, yy)
if c != 0 { seen = 1; if c != 0xff0000 { tinted = 0 } }
} }
print(bi((seen == 1) and (tinted == 1))) # 1 — every opaque pixel took the tint
var table = 0
for yy in 40 .. 72 { for xx in 160 .. 176 { if Screen.pixel(xx, yy) != 0 { table = 1 } } }
print(table) # 0 — without atlas: 1 the id is a table id (nothing loaded there)
}
}