Merge Phase 6e: unify builtin naming

This commit is contained in:
Orkun ÇAKILKAYA 2026-08-27 23:38:10 +03:00
commit 573813609c
16 changed files with 122 additions and 122 deletions

View file

@ -121,7 +121,7 @@ handlers:
```ludic ```ludic
handler Boot phase Start { handler Boot phase Start {
setreg(R_FONT, font_load("…Arial.ttf")) set_reg(R_FONT, font_load("…Arial.ttf"))
ui_build() # construct the tree (loads skins/images) ui_build() # construct the tree (loads skins/images)
ui_open(UI_MainMenu) # make it active, focus the first button ui_open(UI_MainMenu) # make it active, focus the first button
} }
@ -519,7 +519,7 @@ machine R_PHASE {
States **number themselves by declaration order** (`KnightMenu` is `0`, States **number themselves by declaration order** (`KnightMenu` is `0`,
`KnightResolve` is `1`, …) — no magic constants. (An explicit `state Name = expr` `KnightResolve` is `1`, …) — no magic constants. (An explicit `state Name = expr`
is still accepted when a state needs a specific value.) A `machine <reg>` reads is still accepted when a state needs a specific value.) A `machine <reg>` reads
`reg(<reg>)` to pick the state; `become Name` compiles to `setreg(<reg>, <Name's `reg(<reg>)` to pick the state; `become Name` compiles to `set_reg(<reg>, <Name's
value>)`. Both lower to plain branches (and `match` runs on the native LLVM value>)`. Both lower to plain branches (and `match` runs on the native LLVM
backend too). backend too).
@ -538,7 +538,7 @@ if reg(R_MODE) == Mode.Battle { … }
A variant is a **compile-time `int`** accessed as `Enum.Variant` (`Action.Guard` A variant is a **compile-time `int`** accessed as `Enum.Variant` (`Action.Guard`
is `1`), numbered from `0` by declaration order, so it works anywhere an int does is `1`), numbered from `0` by declaration order, so it works anywhere an int does
— `match` patterns, comparisons, `setreg`. Enums are a naming layer over `int`: — `match` patterns, comparisons, `set_reg`. Enums are a naming layer over `int`:
there is no distinct enum runtime type yet, so an enum value lives in an ordinary there is no distinct enum runtime type yet, so an enum value lives in an ordinary
`int` or register (and is saved with it). See `examples/chronorift/combat.ludic`, `int` or register (and is saved with it). See `examples/chronorift/combat.ludic`,
whose battle menus dispatch on `KnightAct`/`MageAct` instead of `0..3`. whose battle menus dispatch on `KnightAct`/`MageAct` instead of `0..3`.
@ -571,9 +571,9 @@ ints (`0xff8800`).
# UI ui_build() ui_open(id) ui_tick(key) ui_render() # UI ui_build() ui_open(id) ui_tick(key) ui_render()
# ui_clicked(id)->bool ui_set_text(id,str) ui_set_int(id,n) # ui_clicked(id)->bool ui_set_text(id,str) ui_set_int(id,n)
# ui_focus(id) ui_focused()->int ui_visible(id,bool) # ui_focus(id) ui_focused()->int ui_visible(id,bool)
# assets load_png(path)->id (decodes a PNG; returns a 16x16 sprite id) # assets png_load(path)->id (decodes a PNG; returns a 16x16 sprite id)
# input key()->int (current frame's key code, 0 if none) # input key()->int (current frame's key code, 0 if none)
# state reg(i)->int setreg(i,v) (64 integer resources shared by handlers) # state reg(i)->int set_reg(i,v) (64 integer resources shared by handlers)
# entity self()->entity # entity self()->entity
# save save() load()->bool (binary snapshot of the whole ECS World) # save save() load()->bool (binary snapshot of the whole ECS World)
# control quit() print_int(i) # control quit() print_int(i)
@ -670,7 +670,7 @@ self-hosting; their lowerings are in
> `layer`, and the `on enter` / `on exit` hooks are a design target: the > `layer`, and the `on enter` / `on exit` hooks are a design target: the
> compiler has no `scene` declaration and [`examples/scenes.ludic`](examples/scenes.ludic) > compiler has no `scene` declaration and [`examples/scenes.ludic`](examples/scenes.ludic)
> does not compile today. Games > does not compile today. Games
> that need mutually-exclusive states use a mode register (`reg`/`setreg`) with a > that need mutually-exclusive states use a mode register (`reg`/`set_reg`) with a
> `machine`, as `examples/chronorift` does. This section describes the intended > `machine`, as `examples/chronorift` does. This section describes the intended
> syntax for when scene support lands. > syntax for when scene support lands.

View file

@ -167,7 +167,7 @@ Controls:
- [x] Cross-compilation to ELF (x86-64, aarch64) and Windows COFF - [x] Cross-compilation to ELF (x86-64, aarch64) and Windows COFF
- [x] ECS runtime (properties, systems, phases, queries, entity pooling) - [x] ECS runtime (properties, systems, phases, queries, entity pooling)
- [x] Windowed 2D rendering (Cocoa driven from LLVM IR) + headless PPM verification - [x] Windowed 2D rendering (Cocoa driven from LLVM IR) + headless PPM verification
- [x] CC0 Kenney PNG sprites (`load_png`, decoder written in Ludic) + scrolling camera - [x] CC0 Kenney PNG sprites (`png_load`, decoder written in Ludic) + scrolling camera
- [x] Overworld: tilemap, movement, collision - [x] Overworld: tilemap, movement, collision
- [x] Random encounters + turn-based battle (HP/MP, seeded-RNG damage) - [x] Random encounters + turn-based battle (HP/MP, seeded-RNG damage)
- [x] Party + **local co-op** (P1 Knight, P2 Mage, per-player turns) - [x] Party + **local co-op** (P1 Knight, P2 Mage, per-player turns)

View file

@ -52,7 +52,7 @@ Verified against the self-hosted compiler ([selfhost/parse.ludic](selfhost/parse
12. Three overlapping control families — `if`/`when`, `match`, `machine`/`become` 12. Three overlapping control families — `if`/`when`, `match`, `machine`/`become`
— and `enter` reuses `become`'s AST node ([parse.ludic:176-177](selfhost/parse.ludic)). *(Phase 4: `if`/`when` kept by choice; magic-int dispatch resolved)* — and `enter` reuses `become`'s AST node ([parse.ludic:176-177](selfhost/parse.ludic)). *(Phase 4: `if`/`when` kept by choice; magic-int dispatch resolved)*
13. Typed components/structs exist, but real state lives in 64 untyped int 13. Typed components/structs exist, but real state lives in 64 untyped int
registers (`reg`/`setreg`), so `machine`/`match` dispatch on magic numbers. *(✅ Phase 4: auto-numbered states + `enum` name the values)* registers (`reg`/`set_reg`), so `machine`/`match` dispatch on magic numbers. *(✅ Phase 4: auto-numbered states + `enum` name the values)*
--- ---

View file

@ -19,32 +19,32 @@ handler Battle phase Update {
if pt.slot == 0 { if pt.slot == 0 {
if st.hp <= 0 { if st.hp <= 0 {
become MageMenu become MageMenu
setreg(R_CUR, 0) set_reg(R_CUR, 0)
} else { } else {
if k == 'w' { setreg(R_CUR, max(0, reg(R_CUR) - 1)) } if k == 'w' { set_reg(R_CUR, max(0, reg(R_CUR) - 1)) }
if k == 's' { setreg(R_CUR, min(3, reg(R_CUR) + 1)) } if k == 's' { set_reg(R_CUR, min(3, reg(R_CUR) + 1)) }
if is_confirm(k) { if is_confirm(k) {
match reg(R_CUR) { match reg(R_CUR) {
KnightAct.Attack => { KnightAct.Attack => {
let d = max(1, st.atk - e.def + rng_range(0, 4)) let d = max(1, st.atk - e.def + rng_range(0, 4))
e.hp = e.hp - d e.hp = e.hp - d
setreg(R_PDMG, d) set_reg(R_PDMG, d)
} }
KnightAct.Guard => { st.guard = 1; setreg(R_PDMG, 0) } KnightAct.Guard => { st.guard = 1; set_reg(R_PDMG, 0) }
KnightAct.Item => { KnightAct.Item => {
if reg(R_POTION) > 0 { if reg(R_POTION) > 0 {
setreg(R_POTION, reg(R_POTION) - 1) set_reg(R_POTION, reg(R_POTION) - 1)
st.hp = min(st.maxhp, st.hp + 24) st.hp = min(st.maxhp, st.hp + 24)
setreg(R_PDMG, 24) set_reg(R_PDMG, 24)
} else { setreg(R_PDMG, 0) } } else { set_reg(R_PDMG, 0) }
} }
KnightAct.Flee => { KnightAct.Flee => {
if rng_chance(50) { e.hp = 0; setreg(R_MODE, 0) } if rng_chance(50) { e.hp = 0; set_reg(R_MODE, 0) }
else { setreg(R_PDMG, 0) } else { set_reg(R_PDMG, 0) }
} }
} }
become KnightResolve become KnightResolve
setreg(R_CUR, 0) set_reg(R_CUR, 0)
} }
} }
} }
@ -55,13 +55,13 @@ handler Battle phase Update {
if k != 0 { if k != 0 {
if e.hp <= 0 { if e.hp <= 0 {
grant_xp(e.xp) grant_xp(e.xp)
setreg(R_GOLD, reg(R_GOLD) + e.xp) set_reg(R_GOLD, reg(R_GOLD) + e.xp)
if reg(R_ETYPE) == 2 { setreg(R_BOSS, 1); setreg(R_MODE, 3); setreg(R_ACK, 0) } if reg(R_ETYPE) == 2 { set_reg(R_BOSS, 1); set_reg(R_MODE, 3); set_reg(R_ACK, 0) }
else { setreg(R_MODE, 0) } else { set_reg(R_MODE, 0) }
become KnightMenu become KnightMenu
} else { } else {
become MageMenu become MageMenu
setreg(R_CUR, 0) set_reg(R_CUR, 0)
} }
} }
} }
@ -72,14 +72,14 @@ handler Battle phase Update {
if st.hp <= 0 { if st.hp <= 0 {
become EnemyTurn become EnemyTurn
} else { } else {
if k == 'i' { setreg(R_CUR, max(0, reg(R_CUR) - 1)) } if k == 'i' { set_reg(R_CUR, max(0, reg(R_CUR) - 1)) }
if k == 'k' { setreg(R_CUR, min(2, reg(R_CUR) + 1)) } if k == 'k' { set_reg(R_CUR, min(2, reg(R_CUR) + 1)) }
if k == 'j' { if k == 'j' {
match reg(R_CUR) { match reg(R_CUR) {
MageAct.Attack => { MageAct.Attack => {
let d = max(1, st.atk + 5 - e.def + rng_range(0, 5)) let d = max(1, st.atk + 5 - e.def + rng_range(0, 5))
e.hp = e.hp - d e.hp = e.hp - d
setreg(R_PDMG, d) set_reg(R_PDMG, d)
} }
MageAct.Heal => { MageAct.Heal => {
if st.mp >= 4 { if st.mp >= 4 {
@ -87,13 +87,13 @@ handler Battle phase Update {
for (t2, p2) in query [Stats, Party] { for (t2, p2) in query [Stats, Party] {
if p2.slot == 0 { t2.hp = min(t2.maxhp, t2.hp + 16) } if p2.slot == 0 { t2.hp = min(t2.maxhp, t2.hp + 16) }
} }
setreg(R_PDMG, 16) set_reg(R_PDMG, 16)
} else { setreg(R_PDMG, 0) } } else { set_reg(R_PDMG, 0) }
} }
MageAct.Guard => { st.guard = 1; setreg(R_PDMG, 0) } MageAct.Guard => { st.guard = 1; set_reg(R_PDMG, 0) }
} }
become MageResolve become MageResolve
setreg(R_CUR, 0) set_reg(R_CUR, 0)
} }
} }
} }
@ -104,9 +104,9 @@ handler Battle phase Update {
if k != 0 { if k != 0 {
if e.hp <= 0 { if e.hp <= 0 {
grant_xp(e.xp) grant_xp(e.xp)
setreg(R_GOLD, reg(R_GOLD) + e.xp) set_reg(R_GOLD, reg(R_GOLD) + e.xp)
if reg(R_ETYPE) == 2 { setreg(R_BOSS, 1); setreg(R_MODE, 3); setreg(R_ACK, 0) } if reg(R_ETYPE) == 2 { set_reg(R_BOSS, 1); set_reg(R_MODE, 3); set_reg(R_ACK, 0) }
else { setreg(R_MODE, 0) } else { set_reg(R_MODE, 0) }
become KnightMenu become KnightMenu
} else { } else {
become EnemyTurn become EnemyTurn
@ -123,7 +123,7 @@ handler Battle phase Update {
let ed = max(1, e.atk - t3.def + rng_range(0, 4)) let ed = max(1, e.atk - t3.def + rng_range(0, 4))
if t3.guard == 1 { ed = ed / 2; t3.guard = 0 } if t3.guard == 1 { ed = ed / 2; t3.guard = 0 }
t3.hp = t3.hp - ed t3.hp = t3.hp - ed
setreg(R_EDMG, ed) set_reg(R_EDMG, ed)
done = 1 done = 1
} }
} }
@ -132,10 +132,10 @@ handler Battle phase Update {
for (t4) in query [Stats, {Party}] { for (t4) in query [Stats, {Party}] {
if t4.hp > 0 { alive = alive + 1 } if t4.hp > 0 { alive = alive + 1 }
} }
if alive == 0 { setreg(R_MODE, 2); setreg(R_ACK, 0) } if alive == 0 { set_reg(R_MODE, 2); set_reg(R_ACK, 0) }
else { else {
become KnightMenu become KnightMenu
setreg(R_CUR, 0) set_reg(R_CUR, 0)
} }
} }
} }
@ -155,18 +155,18 @@ handler CleanBattle phase LateUpdate {
handler Meta phase Update { handler Meta phase Update {
let k = key() let k = key()
if reg(R_MODE) == 2 { if reg(R_MODE) == 2 {
if k == 0 { setreg(R_ACK, 1) } if k == 0 { set_reg(R_ACK, 1) }
if k != 0 { if k != 0 {
if reg(R_ACK) == 1 { if reg(R_ACK) == 1 {
for (h) in query [Stats, {Party}] { h.hp = h.maxhp } for (h) in query [Stats, {Party}] { h.hp = h.maxhp }
setreg(R_MODE, 0) set_reg(R_MODE, 0)
} }
} }
} }
if reg(R_MODE) == 3 { if reg(R_MODE) == 3 {
if k == 0 { setreg(R_ACK, 1) } if k == 0 { set_reg(R_ACK, 1) }
if k != 0 { if k != 0 {
if reg(R_ACK) == 1 { setreg(R_MODE, 0) } if reg(R_ACK) == 1 { set_reg(R_MODE, 0) }
} }
} }
} }

View file

@ -1,18 +1,18 @@
# overworld.ludic — boot, movement/encounter input, and the shop system. # overworld.ludic — boot, movement/encounter input, and the shop system.
handler Boot phase Start { handler Boot phase Start {
load_png("assets/kenney/tiny-town/Tiles/tile_0000.png") # 0 grass png_load("assets/kenney/tiny-town/Tiles/tile_0000.png") # 0 grass
load_png("assets/kenney/tiny-town/Tiles/tile_0004.png") # 1 tree png_load("assets/kenney/tiny-town/Tiles/tile_0004.png") # 1 tree
load_png("assets/kenney/tiny-town/Tiles/tile_0025.png") # 2 path png_load("assets/kenney/tiny-town/Tiles/tile_0025.png") # 2 path
load_png("assets/kenney/tiny-town/Tiles/tile_0073.png") # 3 house png_load("assets/kenney/tiny-town/Tiles/tile_0073.png") # 3 house
load_png("assets/kenney/tiny-town/Tiles/tile_0112.png") # 4 arch png_load("assets/kenney/tiny-town/Tiles/tile_0112.png") # 4 arch
load_png("assets/kenney/tiny-dungeon/Tiles/tile_0096.png") # 5 hero png_load("assets/kenney/tiny-dungeon/Tiles/tile_0096.png") # 5 hero
load_png("assets/kenney/tiny-dungeon/Tiles/tile_0108.png") # 6 slime png_load("assets/kenney/tiny-dungeon/Tiles/tile_0108.png") # 6 slime
load_png("assets/kenney/tiny-dungeon/Tiles/tile_0120.png") # 7 skeleton png_load("assets/kenney/tiny-dungeon/Tiles/tile_0120.png") # 7 skeleton
load_png("assets/kenney/tiny-dungeon/Tiles/tile_0110.png") # 8 boss png_load("assets/kenney/tiny-dungeon/Tiles/tile_0110.png") # 8 boss
load_png("assets/kenney/tiny-dungeon/Tiles/tile_0084.png") # 9 mage png_load("assets/kenney/tiny-dungeon/Tiles/tile_0084.png") # 9 mage
load_png("assets/kenney/tiny-dungeon/Tiles/tile_0048.png") # 10 floor png_load("assets/kenney/tiny-dungeon/Tiles/tile_0048.png") # 10 floor
load_png("assets/kenney/tiny-dungeon/Tiles/tile_0026.png") # 11 wall png_load("assets/kenney/tiny-dungeon/Tiles/tile_0026.png") # 11 wall
build_field(0) build_field(0)
spawn Player { spawn Player {
@ -25,12 +25,12 @@ handler Boot phase Start {
Party { slot: 1 } Party { slot: 1 }
Stats { hp: 26, maxhp: 26, mp: 16, maxmp: 16, atk: 8, def: 4, lvl: 1, xp: 0, guard: 0 } Stats { hp: 26, maxhp: 26, mp: 16, maxmp: 16, atk: 8, def: 4, lvl: 1, xp: 0, guard: 0 }
} }
setreg(R_MODE, 0) set_reg(R_MODE, 0)
setreg(R_ENC, 4) set_reg(R_ENC, 4)
setreg(R_MAP, 0) set_reg(R_MAP, 0)
setreg(R_BOSS, 0) set_reg(R_BOSS, 0)
setreg(R_GOLD, 20) set_reg(R_GOLD, 20)
setreg(R_POTION, 2) set_reg(R_POTION, 2)
} }
handler Control phase Input { handler Control phase Input {
@ -57,43 +57,43 @@ handler Control phase Input {
build_dungeon(0) build_dungeon(0)
p.x = 3 p.x = 3
p.y = 2 p.y = 2
setreg(R_MAP, 1) set_reg(R_MAP, 1)
} }
if tc == 'S' { if tc == 'S' {
build_field(0) build_field(0)
p.x = 36 p.x = 36
p.y = 3 p.y = 3
setreg(R_MAP, 0) set_reg(R_MAP, 0)
} }
if tc == 'B' { if tc == 'B' {
if reg(R_BOSS) == 0 { if reg(R_BOSS) == 0 {
setreg(R_ETYPE, 2) set_reg(R_ETYPE, 2)
spawn Enemy { spawn Enemy {
Actor { kind: 8 } Actor { kind: 8 }
Stats { hp: 120, maxhp: 120, mp: 0, maxmp: 0, atk: 18, def: 10, lvl: 5, xp: 300, guard: 0 } Stats { hp: 120, maxhp: 120, mp: 0, maxmp: 0, atk: 18, def: 10, lvl: 5, xp: 300, guard: 0 }
} }
setreg(R_MODE, 1) set_reg(R_MODE, 1)
setreg(R_PHASE, 0) set_reg(R_PHASE, 0)
setreg(R_CUR, 0) set_reg(R_CUR, 0)
} }
} }
if tc == 'h' { if tc == 'h' {
setreg(R_MODE, 4) set_reg(R_MODE, 4)
setreg(R_CUR, 0) set_reg(R_CUR, 0)
} }
if tc != 'D' { if tc != 'D' {
if tc != 'S' { if tc != 'S' {
if tc != 'B' { if tc != 'B' {
if tc != 'h' { if tc != 'h' {
setreg(R_ENC, reg(R_ENC) - 1) set_reg(R_ENC, reg(R_ENC) - 1)
if reg(R_ENC) <= 0 { if reg(R_ENC) <= 0 {
setreg(R_ENC, rng_range(4, 9)) set_reg(R_ENC, rng_range(4, 9))
let et = 0 let et = 0
if reg(R_MAP) == 1 { et = 1 } if reg(R_MAP) == 1 { et = 1 }
if reg(R_MAP) == 0 { if reg(R_MAP) == 0 {
if rng_chance(40) { et = 1 } if rng_chance(40) { et = 1 }
} }
setreg(R_ETYPE, et) set_reg(R_ETYPE, et)
if et == 0 { if et == 0 {
spawn Enemy { spawn Enemy {
Actor { kind: 6 } Actor { kind: 6 }
@ -106,9 +106,9 @@ handler Control phase Input {
Stats { hp: 30, maxhp: 30, mp: 0, maxmp: 0, atk: 11, def: 5, lvl: 1, xp: 15, guard: 0 } Stats { hp: 30, maxhp: 30, mp: 0, maxmp: 0, atk: 11, def: 5, lvl: 1, xp: 15, guard: 0 }
} }
} }
setreg(R_MODE, 1) set_reg(R_MODE, 1)
setreg(R_PHASE, 0) set_reg(R_PHASE, 0)
setreg(R_CUR, 0) set_reg(R_CUR, 0)
} }
} }
} }
@ -125,16 +125,16 @@ handler Control phase Input {
handler Shop phase Update { handler Shop phase Update {
if reg(R_MODE) == 4 { if reg(R_MODE) == 4 {
let k = key() let k = key()
if k == 'w' { setreg(R_CUR, max(0, reg(R_CUR) - 1)) } if k == 'w' { set_reg(R_CUR, max(0, reg(R_CUR) - 1)) }
if k == 's' { setreg(R_CUR, min(1, reg(R_CUR) + 1)) } if k == 's' { set_reg(R_CUR, min(1, reg(R_CUR) + 1)) }
if is_confirm(k) { if is_confirm(k) {
if reg(R_CUR) == 0 { if reg(R_CUR) == 0 {
if reg(R_GOLD) >= 10 { if reg(R_GOLD) >= 10 {
setreg(R_GOLD, reg(R_GOLD) - 10) set_reg(R_GOLD, reg(R_GOLD) - 10)
setreg(R_POTION, reg(R_POTION) + 1) set_reg(R_POTION, reg(R_POTION) + 1)
} }
} }
if reg(R_CUR) == 1 { setreg(R_MODE, 0) } if reg(R_CUR) == 1 { set_reg(R_MODE, 0) }
} }
} }
} }

View file

@ -20,7 +20,7 @@ program Menu {
} }
handler Boot phase Start { handler Boot phase Start {
setreg(R_FONT, font_load("/System/Library/Fonts/Supplemental/Arial.ttf")) set_reg(R_FONT, font_load("/System/Library/Fonts/Supplemental/Arial.ttf"))
ui_build() ui_build()
ui_open(UI_MainMenu) ui_open(UI_MainMenu)
} }

View file

@ -7,14 +7,14 @@
program SceneDemo { program SceneDemo {
const R_N: int = 0 const R_N: int = 0
handler Boot phase Start { setreg(R_N, 0); print_int(1000) } handler Boot phase Start { set_reg(R_N, 0); print_int(1000) }
scene Title start { scene Title start {
on enter { print_int(1) } on enter { print_int(1) }
on exit { print_int(2) } on exit { print_int(2) }
layer Main { layer Main {
handler Tick phase Update { handler Tick phase Update {
setreg(R_N, reg(R_N) + 1) set_reg(R_N, reg(R_N) + 1)
print_int(100 + reg(R_N)) print_int(100 + reg(R_N))
if reg(R_N) >= 2 { become Play } if reg(R_N) >= 2 { become Play }
} }
@ -22,11 +22,11 @@ program SceneDemo {
} }
scene Play { scene Play {
on enter { print_int(3); setreg(R_N, 0) } on enter { print_int(3); set_reg(R_N, 0) }
on exit { print_int(4) } on exit { print_int(4) }
layer World { layer World {
handler Step phase Update { handler Step phase Update {
setreg(R_N, reg(R_N) + 1) set_reg(R_N, reg(R_N) + 1)
print_int(200 + reg(R_N)) print_int(200 + reg(R_N))
if reg(R_N) >= 2 { quit() } if reg(R_N) >= 2 { quit() }
} }

View file

@ -29,13 +29,13 @@ program Snake {
spawn S0 { Seg { order: 0 }; Pos { x: 10, y: 7 } } spawn S0 { Seg { order: 0 }; Pos { x: 10, y: 7 } }
spawn S1 { Seg { order: 1 }; Pos { x: 9, y: 7 } } spawn S1 { Seg { order: 1 }; Pos { x: 9, y: 7 } }
spawn S2 { Seg { order: 2 }; Pos { x: 8, y: 7 } } spawn S2 { Seg { order: 2 }; Pos { x: 8, y: 7 } }
setreg(R_DIR, 3) set_reg(R_DIR, 3)
setreg(R_LEN, 3) set_reg(R_LEN, 3)
setreg(R_TICK, 0) set_reg(R_TICK, 0)
setreg(R_MODE, 0) set_reg(R_MODE, 0)
setreg(R_SCORE, 0) set_reg(R_SCORE, 0)
setreg(R_FX, 14) set_reg(R_FX, 14)
setreg(R_FY, 7) set_reg(R_FY, 7)
return 0 return 0
} }
@ -44,25 +44,25 @@ program Snake {
handler Input phase Input { handler Input phase Input {
let k = key() let k = key()
if reg(R_MODE) == 0 { if reg(R_MODE) == 0 {
if k == 'w' { if reg(R_DIR) != 1 { setreg(R_DIR, 0) } } if k == 'w' { if reg(R_DIR) != 1 { set_reg(R_DIR, 0) } }
if k == 's' { if reg(R_DIR) != 0 { setreg(R_DIR, 1) } } if k == 's' { if reg(R_DIR) != 0 { set_reg(R_DIR, 1) } }
if k == 'a' { if reg(R_DIR) != 3 { setreg(R_DIR, 2) } } if k == 'a' { if reg(R_DIR) != 3 { set_reg(R_DIR, 2) } }
if k == 'd' { if reg(R_DIR) != 2 { setreg(R_DIR, 3) } } if k == 'd' { if reg(R_DIR) != 2 { set_reg(R_DIR, 3) } }
} }
if reg(R_MODE) == 1 { if reg(R_MODE) == 1 {
if k == 0 { setreg(R_ACK, 1) } if k == 0 { set_reg(R_ACK, 1) }
if k != 0 { if reg(R_ACK) == 1 { reset(0) } } if k != 0 { if reg(R_ACK) == 1 { reset(0) } }
} }
} }
handler Move phase Update { handler Move phase Update {
if reg(R_MODE) == 0 { if reg(R_MODE) == 0 {
setreg(R_TICK, reg(R_TICK) + 1) set_reg(R_TICK, reg(R_TICK) + 1)
if reg(R_TICK) >= 6 { if reg(R_TICK) >= 6 {
setreg(R_TICK, 0) set_reg(R_TICK, 0)
let len = reg(R_LEN) let len = reg(R_LEN)
for (pt, stg) in query [Pos, Seg] { for (pt, stg) in query [Pos, Seg] {
if stg.order == len - 1 { setreg(R_TX, pt.x); setreg(R_TY, pt.y) } if stg.order == len - 1 { set_reg(R_TX, pt.x); set_reg(R_TY, pt.y) }
} }
for step in 0 .. len { for step in 0 .. len {
let i = len - 1 - step let i = len - 1 - step
@ -76,35 +76,35 @@ program Snake {
} }
} }
} }
setreg(R_ATE, 0) set_reg(R_ATE, 0)
for (ph, sh) in query [Pos, Seg] { for (ph, sh) in query [Pos, Seg] {
if sh.order == 0 { if sh.order == 0 {
if reg(R_DIR) == 0 { ph.y = ph.y - 1 } if reg(R_DIR) == 0 { ph.y = ph.y - 1 }
if reg(R_DIR) == 1 { ph.y = ph.y + 1 } if reg(R_DIR) == 1 { ph.y = ph.y + 1 }
if reg(R_DIR) == 2 { ph.x = ph.x - 1 } if reg(R_DIR) == 2 { ph.x = ph.x - 1 }
if reg(R_DIR) == 3 { ph.x = ph.x + 1 } if reg(R_DIR) == 3 { ph.x = ph.x + 1 }
if ph.x < 0 { setreg(R_MODE, 1) } if ph.x < 0 { set_reg(R_MODE, 1) }
if ph.x >= GW { setreg(R_MODE, 1) } if ph.x >= GW { set_reg(R_MODE, 1) }
if ph.y < 0 { setreg(R_MODE, 1) } if ph.y < 0 { set_reg(R_MODE, 1) }
if ph.y >= GH { setreg(R_MODE, 1) } if ph.y >= GH { set_reg(R_MODE, 1) }
if ph.x == reg(R_FX) { if ph.y == reg(R_FY) { setreg(R_ATE, 1) } } if ph.x == reg(R_FX) { if ph.y == reg(R_FY) { set_reg(R_ATE, 1) } }
} }
} }
for (ph, sh) in query [Pos, Seg] { for (ph, sh) in query [Pos, Seg] {
if sh.order == 0 { if sh.order == 0 {
for (pb, sb) in query [Pos, Seg] { for (pb, sb) in query [Pos, Seg] {
if sb.order > 0 { if sb.order > 0 {
if pb.x == ph.x { if pb.y == ph.y { setreg(R_MODE, 1) } } if pb.x == ph.x { if pb.y == ph.y { set_reg(R_MODE, 1) } }
} }
} }
} }
} }
if reg(R_ATE) == 1 { if reg(R_ATE) == 1 {
setreg(R_SCORE, reg(R_SCORE) + 1) set_reg(R_SCORE, reg(R_SCORE) + 1)
spawn Body { Seg { order: len }; Pos { x: reg(R_TX), y: reg(R_TY) } } spawn Body { Seg { order: len }; Pos { x: reg(R_TX), y: reg(R_TY) } }
setreg(R_LEN, len + 1) set_reg(R_LEN, len + 1)
setreg(R_FX, rng_range(0, GW - 1)) set_reg(R_FX, rng_range(0, GW - 1))
setreg(R_FY, rng_range(0, GH - 1)) set_reg(R_FY, rng_range(0, GH - 1))
} }
} }
} }

View file

@ -178,7 +178,7 @@ fn rt_reg(i: int) -> int {
return peek32(rt_regs, i) return peek32(rt_regs, i)
} }
fn rt_setreg(i: int, v: int) -> void { fn rt_set_reg(i: int, v: int) -> void {
if i < 0 { return } if i < 0 { return }
if i >= 64 { return } if i >= 64 { return }
poke32(rt_regs, i, v) poke32(rt_regs, i, v)

View file

@ -316,7 +316,7 @@ fn rt_draw_9slice(id: int, dx: int, dy: int, dw: int, dh: int, inset: int) -> vo
} }
# ---- sprites (16x16 art) -------------------------------------------------- # ---- sprites (16x16 art) --------------------------------------------------
fn rt_load_png(path: str) -> int { fn rt_png_load(path: str) -> int {
if spr_n >= SPR_MAX { return 0 - 1 } if spr_n >= SPR_MAX { return 0 - 1 }
if rt_decode_png(path) == false { return 0 - 1 } if rt_decode_png(path) == false { return 0 - 1 }
let id = spr_n let id = spr_n
@ -376,7 +376,7 @@ fn rt_hexval(c: int) -> int {
return 0 - 1 return 0 - 1
} }
fn rt_load_sprites(path: str) -> void { fn rt_sprites_load(path: str) -> void {
let d = rt_read_file(path) let d = rt_read_file(path)
if ptr_is_null(d) { spr_n = 0; return } if ptr_is_null(d) { spr_n = 0; return }
let size = rt_file_len let size = rt_file_len

View file

@ -1,6 +1,6 @@
# emit_machine.ludic — `machine <reg> { state Name = v { .. } }` dispatches on a # emit_machine.ludic — `machine <reg> { state Name = v { .. } }` dispatches on a
# register's value; `become Name` stores the target state's value back. Both are # register's value; `become Name` stores the target state's value back. Both are
# reg()/setreg() calls, resolved to the runtime like any other builtin. # reg()/set_reg() calls, resolved to the runtime like any other builtin.
fn emit_machine(st: Node) -> void { fn emit_machine(st: Node) -> void {
let regv = emit_expr(st.a) let regv = emit_expr(st.a)
@ -35,5 +35,5 @@ fn emit_become(st: Node) -> void {
if ptr_is_null(target) { perr(sconcat("become: no state ", st.s)) } if ptr_is_null(target) { perr(sconcat("become: no state ", st.s)) }
let regv = emit_expr(m.a) let regv = emit_expr(m.a)
let sv = emit_expr(target.b) let sv = emit_expr(target.b)
emit(" call void @fn_rt_setreg(i32 "); emit(regv.code); emit(", i32 "); emit(sv.code); emit(")\n") emit(" call void @fn_rt_set_reg(i32 "); emit(regv.code); emit(", i32 "); emit(sv.code); emit(")\n")
} }

View file

@ -1465,7 +1465,7 @@ declare void @win_close()
@.str1390 = private unnamed_addr constant [3 x i8] c":\0A\00" @.str1390 = private unnamed_addr constant [3 x i8] c":\0A\00"
@.str1391 = private unnamed_addr constant [27 x i8] c"'become' outside a machine\00" @.str1391 = private unnamed_addr constant [27 x i8] c"'become' outside a machine\00"
@.str1392 = private unnamed_addr constant [18 x i8] c"become: no state \00" @.str1392 = private unnamed_addr constant [18 x i8] c"become: no state \00"
@.str1393 = private unnamed_addr constant [31 x i8] c" call void @fn_rt_setreg(i32 \00" @.str1393 = private unnamed_addr constant [32 x i8] c" call void @fn_rt_set_reg(i32 \00"
@.str1394 = private unnamed_addr constant [7 x i8] c", i32 \00" @.str1394 = private unnamed_addr constant [7 x i8] c", i32 \00"
@.str1395 = private unnamed_addr constant [3 x i8] c")\0A\00" @.str1395 = private unnamed_addr constant [3 x i8] c")\0A\00"
@.str1396 = private unnamed_addr constant [4 x i8] c"%io\00" @.str1396 = private unnamed_addr constant [4 x i8] c"%io\00"

View file

@ -60,10 +60,10 @@ object LudicVocabulary {
"min", "max", "abs", "clamp", "seed", "rng_range", "rng_chance", "fx", "flr", "min", "max", "abs", "clamp", "seed", "rng_range", "rng_chance", "fx", "flr",
"map_size", "map_row", "tile", "clear", "present", "fill_rect", "frame_rect", "map_size", "map_row", "tile", "clear", "present", "fill_rect", "frame_rect",
"put_px", "text", "text_int", "font_load", "text_ttf", "text_w", "text_h", "put_px", "text", "text_int", "font_load", "text_ttf", "text_w", "text_h",
"image_load", "draw_image", "draw_image_scaled", "draw_9slice", "load_png", "image_load", "draw_image", "draw_image_scaled", "draw_9slice", "png_load",
"load_sprites", "draw_sprite", "draw_sprite_scaled", "ui_build", "ui_open", "sprites_load", "draw_sprite", "draw_sprite_scaled", "ui_build", "ui_open",
"ui_tick", "ui_render", "ui_clicked", "ui_set_text", "ui_set_int", "ui_focus", "ui_tick", "ui_render", "ui_clicked", "ui_set_text", "ui_set_int", "ui_focus",
"ui_focused", "ui_visible", "key", "reg", "setreg", "self", "save", "load", "ui_focused", "ui_visible", "key", "reg", "set_reg", "self", "save", "load",
"status", "print_int", "quit", "status", "print_int", "quit",
// compiler intrinsics: the floor the Ludic-written runtime stands on // compiler intrinsics: the floor the Ludic-written runtime stands on
"mem_alloc", "mem_realloc", "mem_free", "mem_copy", "mem_set", "peek8", "poke8", "peek32", "mem_alloc", "mem_realloc", "mem_free", "mem_copy", "mem_set", "peek8", "poke8", "peek32",

View file

@ -193,7 +193,7 @@
{ {
"comment": "the runtime surface — every name here resolves to rt_<name> in runtime/native", "comment": "the runtime surface — every name here resolves to rt_<name> in runtime/native",
"name": "support.function.builtin.ludic", "name": "support.function.builtin.ludic",
"match": "\\b(min|max|abs|clamp|seed|rng_range|rng_chance|fx|flr|map_size|map_row|tile|clear|present|fill_rect|frame_rect|put_px|text|text_int|font_load|text_ttf|text_w|text_h|image_load|draw_image|draw_image_scaled|draw_9slice|load_png|load_sprites|draw_sprite|draw_sprite_scaled|ui_build|ui_open|ui_tick|ui_render|ui_clicked|ui_set_text|ui_set_int|ui_focus|ui_focused|ui_visible|key|reg|setreg|self|save|load|status|print_int|quit)\\b(?=\\s*\\()" "match": "\\b(min|max|abs|clamp|seed|rng_range|rng_chance|fx|flr|map_size|map_row|tile|clear|present|fill_rect|frame_rect|put_px|text|text_int|font_load|text_ttf|text_w|text_h|image_load|draw_image|draw_image_scaled|draw_9slice|png_load|sprites_load|draw_sprite|draw_sprite_scaled|ui_build|ui_open|ui_tick|ui_render|ui_clicked|ui_set_text|ui_set_int|ui_focus|ui_focused|ui_visible|key|reg|set_reg|self|save|load|status|print_int|quit)\\b(?=\\s*\\()"
}, },
{ {
"comment": "compiler intrinsics — these lower straight to libc or the OS", "comment": "compiler intrinsics — these lower straight to libc or the OS",

View file

@ -193,7 +193,7 @@
{ {
"comment": "the runtime surface — every name here resolves to rt_<name> in runtime/native", "comment": "the runtime surface — every name here resolves to rt_<name> in runtime/native",
"name": "support.function.builtin.ludic", "name": "support.function.builtin.ludic",
"match": "\\b(min|max|abs|clamp|seed|rng_range|rng_chance|fx|flr|map_size|map_row|tile|clear|present|fill_rect|frame_rect|put_px|text|text_int|font_load|text_ttf|text_w|text_h|image_load|draw_image|draw_image_scaled|draw_9slice|load_png|load_sprites|draw_sprite|draw_sprite_scaled|ui_build|ui_open|ui_tick|ui_render|ui_clicked|ui_set_text|ui_set_int|ui_focus|ui_focused|ui_visible|key|reg|setreg|self|save|load|status|print_int|quit)\\b(?=\\s*\\()" "match": "\\b(min|max|abs|clamp|seed|rng_range|rng_chance|fx|flr|map_size|map_row|tile|clear|present|fill_rect|frame_rect|put_px|text|text_int|font_load|text_ttf|text_w|text_h|image_load|draw_image|draw_image_scaled|draw_9slice|png_load|sprites_load|draw_sprite|draw_sprite_scaled|ui_build|ui_open|ui_tick|ui_render|ui_clicked|ui_set_text|ui_set_int|ui_focus|ui_focused|ui_visible|key|reg|set_reg|self|save|load|status|print_int|quit)\\b(?=\\s*\\()"
}, },
{ {
"comment": "compiler intrinsics — these lower straight to libc or the OS", "comment": "compiler intrinsics — these lower straight to libc or the OS",

View file

@ -117,8 +117,8 @@ static const LBuiltin LUDIC_BUILTINS[] = {
{"draw_image","draw_image(id: int, x: int, y: int)","Blit an image at its natural size."}, {"draw_image","draw_image(id: int, x: int, y: int)","Blit an image at its natural size."},
{"draw_image_scaled","draw_image_scaled(id: int, x: int, y: int, w: int, h: int)","Blit an image stretched to w x h."}, {"draw_image_scaled","draw_image_scaled(id: int, x: int, y: int, w: int, h: int)","Blit an image stretched to w x h."},
{"draw_9slice","draw_9slice(id: int, x: int, y: int, w: int, h: int, inset: int)","Nine-slice an image across a w x h box."}, {"draw_9slice","draw_9slice(id: int, x: int, y: int, w: int, h: int, inset: int)","Nine-slice an image across a w x h box."},
{"load_png","load_png(path: str) -> int","Decode a PNG as a 16x16 sprite sheet; returns the first sprite id."}, {"png_load","png_load(path: str) -> int","Decode a PNG as a 16x16 sprite sheet; returns the first sprite id."},
{"load_sprites","load_sprites(path: str)","Load the sprite sheet used by draw_sprite."}, {"sprites_load","sprites_load(path: str)","Load the sprite sheet used by draw_sprite."},
{"draw_sprite","draw_sprite(id: int, x: int, y: int)","Blit a sprite."}, {"draw_sprite","draw_sprite(id: int, x: int, y: int)","Blit a sprite."},
{"draw_sprite_scaled","draw_sprite_scaled(id: int, x: int, y: int, scale: int)","Blit a sprite at an integer scale."}, {"draw_sprite_scaled","draw_sprite_scaled(id: int, x: int, y: int, scale: int)","Blit a sprite at an integer scale."},
{"ui_build","ui_build()","Construct every declared `ui` tree (loads skins and images)."}, {"ui_build","ui_build()","Construct every declared `ui` tree (loads skins and images)."},
@ -133,7 +133,7 @@ static const LBuiltin LUDIC_BUILTINS[] = {
{"ui_visible","ui_visible(id: int, on: bool)","Show or hide a widget subtree."}, {"ui_visible","ui_visible(id: int, on: bool)","Show or hide a widget subtree."},
{"key","key() -> int","Key code pressed this frame, 0 if none."}, {"key","key() -> int","Key code pressed this frame, 0 if none."},
{"reg","reg(i: int) -> int","Read one of the 64 integer resources shared by systems."}, {"reg","reg(i: int) -> int","Read one of the 64 integer resources shared by systems."},
{"setreg","setreg(i: int, v: int)","Write one of the 64 integer resources."}, {"set_reg","set_reg(i: int, v: int)","Write one of the 64 integer resources."},
{"self","self() -> entity","The entity of the innermost query loop."}, {"self","self() -> entity","The entity of the innermost query loop."},
{"save","save()","Write a binary snapshot of the whole ECS world."}, {"save","save()","Write a binary snapshot of the whole ECS world."},
{"load","load() -> bool","Restore the snapshot; false if there is none."}, {"load","load() -> bool","Restore the snapshot; false if there is none."},