ludic/examples/library/managers.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

42 lines
2.3 KiB
Text

# managers.ludic — the engine managers: the Map cell API, Sprite facing / run strip /
# flash / blink, IVec2 geometry, List.sample, Input.move_i, Fx, the audio bank,
# Camera.shake_for, timed Assets. Deterministic; prints:
# 35046 46 251 53 900 -1 20 324 0 10 102
program Managers {
import "ludic.core/components.ludic"
import "ludic.shooter/shooter.ludic"
property Tag { n: int = 0 }
model Thing { Position, Body, Sprite, Tag, TopDown }
model WorldRules { Solids }
var frames: int = 0
handler Tick phase Update {
frames += 1
if frames == 1 {
Map.size(10, 8)
Map.fill(glyph: '.')
Map.border(glyph: '#')
Map.rect(x: 4, y: 3, width: 2, height: 2, glyph: '#')
spawn WorldRules { Solids { tile: 16, wall: '#' } }
print(Map.get(x: 0, y: 0) * 1000 + Map.get(x: 5, y: 5)) # 35046: '#', '.'
let cell = Map.random_cell(glyph: '.')
print(Map.get(x: cell.x, y: cell.y)) # 46
let a = IVec2.make(0, 0); let b = IVec2.make(3, 4)
print(IVec2.distance2(a, b) * 10 + IVec2.within(a, b, 5)) # 251
print(IVec2.heading(a, b)) # 53
let p = IVec2.along(a, 0, 10); print(p.x * 100 + p.y) # 900 (the sine table rounds down)
print(IVec2.step(180).x) # -1
print(Angle.diff_degrees(a: 350, b: 10)) # 20
let pool = new []int; push(pool, 7); push(pool, 8); push(pool, 9)
let picks = List.sample(pool, 3); print(len(picks) * 100 + picks[0] + picks[1] + picks[2]) # 324
let m = Input.move_i(); print(m.x + m.y) # 0
Audio.define(name: "hit", path: "nope.wav")
Audio.play(name: "hit")
Camera.shake_for(amount: 3, frames: 2)
Fx.sparks(x: 10, y: 10, color: 0xffffff, count: 4)
Fx.number(x: 20, y: 20, value: 42, color: 0xffff00)
spawn Thing { Position { x: 20, y: 20 }, Sprite { id: 5, move_id: 9, face: 1, flash: 3, blink: 6 }, TopDown { want_x: -1 } }
}
if frames == 3 { print(Map.is_solid(x: 0, y: 0) * 10 + Map.is_solid_at(x: 40, y: 40)) } # 10
if frames == 5 { for (s, t) in query [Sprite, Tag] { print(s.flip * 100 + s.flash * 10 + s.blink) }; quit() } # 102
}
}