docs(api): per-symbol pages, fuzzy search, deep token linking, hover cards
All checks were successful
docs / build-and-deploy (push) Successful in 2s
All checks were successful
docs / build-and-deploy (push) Successful in 2s
Rebuild the API Reference around one page per symbol and richer, verified content.
Pages & navigation
- One HTML page per symbol (kw-*, type-*, phase-*, screen-*, fn-*, annot-*, op-*)
instead of a single scrolling page; namespace overview pages (ns-screen …
ns-color) and a searchable index (api.html) with client-side fuzzy search.
- Sticky-header scroll offset (scroll-margin) so a jumped-to entry/param/color is
never hidden, plus a flash highlight on the scrolled-to target.
Deep linking in every snippet & example
- Namespace members split: `Screen`→namespace page, `fill_rectangle`→method page;
`Color`→palette page, `Charcoal`→its swatch — separately.
- Named arguments (`width:`) link to that parameter's anchor on the method page.
- Hover any token for a summary card built from the real API data (symbols.json).
Content & coverage
- Full authoritative surface documented from the compiler: every keyword, type,
the 6 phases (Start/Input/FixedUpdate/Update/LateUpdate/Render, each its own
page), all 22 annotations, namespace methods with parameter docs, builtins,
the world_* reflection ABI, networking, operators — 155 symbols.
- Longer, clearer explanations; "model"/"model instance" terminology, not "entity";
descriptive identifiers in every example (Position{column,row}, Velocity{delta_x,
delta_y}, Health{current,maximum}, Player/Enemy) — no Pos/Seg/x/dx.
- Accuracy fixes from compiler ground-truth: world_count() takes no arg,
world_query_next(property, cursor) arg order, event fields bind by name; dropped
`when` and `module` (not in the self-hosted parser).
Tooling
- inventory.json + check.py: coverage guard (every symbol has a page), duplicate-
token guard, and broken-link guard — fail CI so docs can't drift.
- validate.py: compiles every ```ludic example against bin/ludicc (158 compile).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
25f987e30d
commit
3c7ec9b016
172 changed files with 5240 additions and 895 deletions
|
|
@ -5,8 +5,34 @@ category: builtins
|
|||
kind: builtin
|
||||
tokens: abs
|
||||
sig: abs(a) -> int
|
||||
tip: Absolute value.
|
||||
tip: The absolute value of an integer (its magnitude, never negative).
|
||||
order: 8
|
||||
---
|
||||
|
||||
Absolute value.
|
||||
Returns the magnitude of an integer, dropping its sign — so <code>abs(-7)</code> and <code>abs(7)</code> both give <code>7</code>. It is handy for distance-style checks where direction does not matter, such as measuring how far two grid cells are apart along one axis before deciding whether something is "close enough". Combine two axis distances to build a simple proximity test. It takes a single integer and returns an integer.
|
||||
|
||||
Parameters:
|
||||
- `a` — the integer whose magnitude you want
|
||||
|
||||
```ludic
|
||||
program Proximity {
|
||||
property Position { column: int = 0, row: int = 0 }
|
||||
model Player { Position }
|
||||
model Enemy { Position }
|
||||
|
||||
handler SpawnActors phase Start {
|
||||
spawn Player { Position { column: 5, row: 5 } }
|
||||
spawn Enemy { Position { column: 8, row: 6 } }
|
||||
}
|
||||
|
||||
handler CheckAdjacency phase Update {
|
||||
for (PlayerPosition) in query [Position, {Player}] {
|
||||
for (EnemyPosition) in query [Position, {Enemy}] {
|
||||
let column_gap = abs(PlayerPosition.column - EnemyPosition.column)
|
||||
let row_gap = abs(PlayerPosition.row - EnemyPosition.row)
|
||||
if column_gap + row_gap <= 1 { print(1) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,12 +1,27 @@
|
|||
---
|
||||
id: fn-arg
|
||||
name: arg_count / arg
|
||||
name: arg
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: arg_count arg
|
||||
sig: arg_count() -> int arg(i) -> str
|
||||
tip: The command line; argv[0] is included.
|
||||
order: 20
|
||||
tokens: arg
|
||||
sig: arg(i) -> str
|
||||
tip: The i-th command-line argument as a string.
|
||||
order: 50
|
||||
---
|
||||
|
||||
Read the process command line: <code>arg_count()</code> is the number of arguments (including the program name at index 0) and <code>arg(i)</code> returns the <code>i</code>th as a string.
|
||||
Returns the command-line argument at index <code>i</code> as a string. Index <code>0</code> is conventionally the program's own name or path, so the first user-supplied argument is at index <code>1</code>. Use it together with <code>arg_count</code> to write configurable command-line tools in Ludic — reading an input filename, a mode flag, or a numeric option. Guard the index against <code>arg_count()</code> before reading so you never ask for an argument that was not passed.
|
||||
|
||||
Parameters:
|
||||
- `i` — the argument index (`0` is the program name)
|
||||
|
||||
```ludic
|
||||
program EchoFirstArg {
|
||||
handler ShowArg phase Start {
|
||||
if arg_count() > 1 {
|
||||
print(arg(1))
|
||||
} else {
|
||||
print("no argument")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
23
docs/language/builtins/fn-arg_count.md
Normal file
23
docs/language/builtins/fn-arg_count.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
id: fn-arg_count
|
||||
name: arg_count
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: arg_count
|
||||
sig: arg_count() -> int
|
||||
tip: The number of command-line arguments, counting the program name.
|
||||
order: 50
|
||||
---
|
||||
|
||||
Returns how many command-line arguments the program received, including the program name at index <code>0</code>. So a program run with no user arguments reports <code>1</code>, and each added argument raises the count by one. Use it to validate that the required arguments were supplied and to bound the index you pass to <code>arg</code>. It takes no arguments and is the natural companion to <code>arg</code> when building command-line tools.
|
||||
|
||||
```ludic
|
||||
program PrintAllArgs {
|
||||
handler ListArgs phase Start {
|
||||
print(arg_count())
|
||||
for index in 0 .. arg_count() {
|
||||
print(arg(index))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -1,12 +1,29 @@
|
|||
---
|
||||
id: fn-bytes
|
||||
name: bytes / words
|
||||
name: bytes
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: bytes words
|
||||
sig: bytes(n) -> ptr / words(n) -> words
|
||||
tip: Allocate a raw buffer of n bytes / n 32-bit words.
|
||||
order: 11
|
||||
tokens: bytes
|
||||
sig: bytes(n) -> ptr
|
||||
tip: Allocate a raw buffer of n bytes and return a pointer to it.
|
||||
order: 50
|
||||
---
|
||||
|
||||
Allocate a raw buffer of n bytes / n 32-bit words.
|
||||
Allocates a raw, byte-addressable buffer of <code>n</code> bytes and returns a pointer to its start. It is a low-level primitive used for I/O and networking scratch space — for example a buffer to serialize the world into, or to receive bytes from a socket. Size it to what you need; a common pattern is <code>bytes(world_size())</code> so the buffer is exactly large enough for a full world snapshot. For a buffer of 32-bit words rather than individual bytes, use <code>words</code> instead.
|
||||
|
||||
Parameters:
|
||||
- `n` — the number of bytes to allocate
|
||||
|
||||
```ludic
|
||||
program SnapshotBuffer {
|
||||
property Position { column: int = 0, row: int = 0 }
|
||||
model Player { Position }
|
||||
|
||||
handler Snapshot phase Start {
|
||||
spawn Player { Position { column: 3, row: 4 } }
|
||||
let buffer = bytes(world_size())
|
||||
let written = world_save(buffer)
|
||||
print(written)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,8 +5,50 @@ category: builtins
|
|||
kind: builtin
|
||||
tokens: clamp
|
||||
sig: clamp(v, lo, hi) -> int
|
||||
tip: Constrain v to [lo, hi].
|
||||
tip: Constrain a value to the inclusive range [lo, hi].
|
||||
order: 9
|
||||
---
|
||||
|
||||
Constrain v to [lo, hi].
|
||||
Constrains a value to a range: it returns <code>lo</code> if <code>v</code> is below the range, <code>hi</code> if it is above, and <code>v</code> unchanged when it already lies within <code>[lo, hi]</code>. The classic use is keeping a moving object on screen or inside a grid, so its position can never run past the walls no matter how fast it moves. It saves you writing a pair of <code>if</code> checks by hand. Make sure <code>lo</code> is not greater than <code>hi</code>.
|
||||
|
||||
Parameters:
|
||||
- `v` — the value to constrain
|
||||
- `lo` — the lowest allowed value (inclusive)
|
||||
- `hi` — the highest allowed value (inclusive)
|
||||
|
||||
```ludic
|
||||
program ClampToGrid {
|
||||
property Position { column: int = 0, row: int = 0 }
|
||||
model Player { Position }
|
||||
|
||||
const GRID_WIDTH: int = 20
|
||||
const GRID_HEIGHT: int = 15
|
||||
const TILE_SIZE: int = 16
|
||||
|
||||
handler SpawnPlayer phase Start {
|
||||
spawn Player { Position { column: 5, row: 5 } }
|
||||
}
|
||||
|
||||
handler ReadInput phase Input {
|
||||
let pressed = Input.key()
|
||||
for (Position) in query [Position, {Player}] {
|
||||
if pressed == 'd' { Position.column = Position.column + 1 }
|
||||
if pressed == 'a' { Position.column = Position.column - 1 }
|
||||
Position.column = clamp(Position.column, 0, GRID_WIDTH - 1)
|
||||
}
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
for (Position) in query [Position, {Player}] {
|
||||
Screen.fill_rectangle(
|
||||
x: Position.column * TILE_SIZE,
|
||||
y: Position.row * TILE_SIZE,
|
||||
width: TILE_SIZE,
|
||||
height: TILE_SIZE,
|
||||
color: Color.LimeGreen)
|
||||
}
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,8 +5,24 @@ category: builtins
|
|||
kind: builtin
|
||||
tokens: exit
|
||||
sig: exit(code)
|
||||
tip: Exit the process immediately with a status code.
|
||||
tip: Terminate the process immediately with a status code.
|
||||
order: 21
|
||||
---
|
||||
|
||||
Terminate the process now with the given status code. Unlike <code>quit()</code>, which ends the game loop cleanly after the current frame, <code>exit</code> stops the program at once.
|
||||
Terminates the process at once with the given status code, without waiting for the current frame to finish. This is a harder stop than <code>quit</code>, which ends the game loop cleanly after the frame completes; use <code>exit</code> for command-line tools and tests that need to signal success or failure to the shell, where <code>0</code> conventionally means success and any non-zero value means an error. Because it stops immediately, drawing queued for the current frame is not presented.
|
||||
|
||||
Parameters:
|
||||
- `code` — the process exit status (`0` for success, non-zero for failure)
|
||||
|
||||
```ludic
|
||||
program RequireArgument {
|
||||
handler CheckArgs phase Start {
|
||||
if arg_count() < 2 {
|
||||
print(0)
|
||||
exit(1)
|
||||
}
|
||||
print(1)
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
---
|
||||
id: fn-file
|
||||
name: file_write / file_stdout / file_stderr
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: file_write file_stdout file_stderr
|
||||
sig: file_stdout() -> ptr file_stderr() -> ptr file_write(handle, buf, len)
|
||||
tip: Low-level output handles and raw writing.
|
||||
order: 25
|
||||
---
|
||||
|
||||
<code>file_stdout()</code> and <code>file_stderr()</code> return the standard stream handles; <code>file_write(handle, buf, len)</code> writes <code>len</code> raw bytes to one. Most code uses <code>print</code> instead — these are the primitive underneath.
|
||||
26
docs/language/builtins/fn-file_stderr.md
Normal file
26
docs/language/builtins/fn-file_stderr.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
id: fn-file_stderr
|
||||
name: file_stderr
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: file_stderr
|
||||
sig: file_stderr() -> ptr
|
||||
tip: The standard-error stream handle for use with file_write.
|
||||
order: 50
|
||||
---
|
||||
|
||||
Returns the handle for the standard-error stream, which you pass to <code>file_write</code> to emit raw bytes. Writing diagnostics and error messages here keeps them separate from normal program output on standard out, so a tool's real results are not mixed with its warnings. It takes no arguments and always refers to the process's standard error. Use it for the error side of command-line tools written in Ludic.
|
||||
|
||||
```ludic
|
||||
program WarnToStderr {
|
||||
handler CheckArgs phase Start {
|
||||
if arg_count() < 2 {
|
||||
let err = file_stderr()
|
||||
let message = "missing argument\n"
|
||||
file_write(err, message, len(message))
|
||||
exit(1)
|
||||
}
|
||||
print("ok")
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/builtins/fn-file_stdout.md
Normal file
22
docs/language/builtins/fn-file_stdout.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: fn-file_stdout
|
||||
name: file_stdout
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: file_stdout
|
||||
sig: file_stdout() -> ptr
|
||||
tip: The standard-output stream handle for use with file_write.
|
||||
order: 50
|
||||
---
|
||||
|
||||
Returns the handle for the standard-output stream, which you pass to <code>file_write</code> to emit raw bytes. Unlike <code>print</code>, which always appends a newline and handles conversion, writing through this handle gives you exact control over what bytes go out and when — useful for tooling and code generators that must emit precise text. It takes no arguments and always refers to the process's standard output. Pair it with <code>file_stderr</code> when you want to separate normal output from diagnostics.
|
||||
|
||||
```ludic
|
||||
program WriteLine {
|
||||
handler Emit phase Start {
|
||||
let out = file_stdout()
|
||||
let message = "built\n"
|
||||
file_write(out, message, len(message))
|
||||
}
|
||||
}
|
||||
```
|
||||
29
docs/language/builtins/fn-file_write.md
Normal file
29
docs/language/builtins/fn-file_write.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
id: fn-file_write
|
||||
name: file_write
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: file_write
|
||||
sig: file_write(handle, buf, len)
|
||||
tip: Write a run of raw bytes to a stream handle.
|
||||
order: 50
|
||||
---
|
||||
|
||||
Writes <code>len</code> bytes from <code>buf</code> to the given stream <code>handle</code>. Get the handle from <code>file_stdout</code> or <code>file_stderr</code>; the buffer is typically a string (whose length you pass with <code>len(buf)</code>) or a raw buffer from <code>bytes</code>. This is the precise, no-frills output primitive that underpins Ludic's own tooling and code generators, where exact bytes matter and the automatic newline of <code>print</code> would get in the way. Pass a byte count that does not exceed the buffer's size.
|
||||
|
||||
Parameters:
|
||||
- `handle` — a stream handle from `file_stdout` or `file_stderr`
|
||||
- `buf` — the bytes to write (a string or a raw buffer)
|
||||
- `len` — how many bytes to write
|
||||
|
||||
```ludic
|
||||
program WriteTwice {
|
||||
handler Emit phase Start {
|
||||
let out = file_stdout()
|
||||
let heading = "SCORE: "
|
||||
let value = "250\n"
|
||||
file_write(out, heading, len(heading))
|
||||
file_write(out, value, len(value))
|
||||
}
|
||||
}
|
||||
```
|
||||
29
docs/language/builtins/fn-flr.md
Normal file
29
docs/language/builtins/fn-flr.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
id: fn-flr
|
||||
name: flr
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: flr
|
||||
sig: flr(x) -> int
|
||||
tip: Floor a fixed-point value down to the nearest integer.
|
||||
order: 50
|
||||
---
|
||||
|
||||
Converts a <code>fixed</code> (Q16.16) value back to an <code>int</code> by discarding the fractional part, rounding toward negative infinity. It is the counterpart to <code>fx</code>: you accumulate motion in fixed-point for sub-pixel smoothness, then <code>flr</code> the result to get the whole-pixel column or row to draw at. Because it floors rather than rounds, <code>flr(fx(3) / fx(2))</code> is <code>1</code>, not <code>2</code>. Use it wherever a fixed value must become an integer coordinate, count, or index.
|
||||
|
||||
Parameters:
|
||||
- `x` — the fixed-point value to floor
|
||||
|
||||
```ludic
|
||||
program FixedToPixels {
|
||||
const TILE_SIZE: int = 16
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
let smooth_column = fx(5) + fx(1) / fx(2)
|
||||
let pixel_x = flr(smooth_column) * TILE_SIZE
|
||||
Screen.fill_rectangle(x: pixel_x, y: 32, width: TILE_SIZE, height: TILE_SIZE, color: Color.LimeGreen)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
---
|
||||
id: fn-font_load
|
||||
name: font_load
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: font_load
|
||||
sig: font_load(path) -> int
|
||||
tip: Load a TrueType font and return a handle for the UI.
|
||||
order: 12
|
||||
---
|
||||
|
||||
Load a TrueType font and return a handle for the UI.
|
||||
|
|
@ -1,12 +1,26 @@
|
|||
---
|
||||
id: fn-fx
|
||||
name: fx / flr
|
||||
name: fx
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: fx flr
|
||||
sig: fx(n) -> fixed / flr(x) -> int
|
||||
tip: Convert between int and fixed-point.
|
||||
order: 10
|
||||
tokens: fx
|
||||
sig: fx(n) -> fixed
|
||||
tip: Lift an integer into a Q16.16 fixed-point value.
|
||||
order: 50
|
||||
---
|
||||
|
||||
Convert between int and fixed-point.
|
||||
Converts an integer into a <code>fixed</code> value (Ludic's Q16.16 fixed-point type), so it can take part in fractional arithmetic. Ludic has no floating point; <code>fixed</code> is how you carry sub-pixel precision for smooth movement and physics-like accumulation. Use <code>fx</code> when you need to combine an <code>int</code> with fixed-point values or divide to get a fraction — for example <code>fx(1) / fx(4)</code> is <code>0.25</code>. Convert back to a whole number for drawing with <code>flr</code>.
|
||||
|
||||
Parameters:
|
||||
- `n` — the integer to lift into fixed-point
|
||||
|
||||
```ludic
|
||||
program SmoothAccumulate {
|
||||
handler ComputeStep phase Start {
|
||||
let full_speed = fx(3)
|
||||
let half_speed = full_speed / fx(2)
|
||||
let two_steps = half_speed + half_speed
|
||||
print(flr(two_steps))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,8 +5,24 @@ category: builtins
|
|||
kind: builtin
|
||||
tokens: getenv
|
||||
sig: getenv(name) -> str
|
||||
tip: Read an environment variable.
|
||||
tip: Read an environment variable, returning empty when it is unset.
|
||||
order: 23
|
||||
---
|
||||
|
||||
Return the value of environment variable <code>name</code> (empty when it is unset).
|
||||
Returns the value of the named environment variable, or an empty string when that variable is not set. Use it to make command-line tools and headless runs configurable without recompiling — reading a data directory, a log level, or a seed from the environment. Check for the empty string to detect an unset variable and fall back to a default. Like the other system builtins, it is aimed at tooling rather than a shipped game loop.
|
||||
|
||||
Parameters:
|
||||
- `name` — the environment variable name to read
|
||||
|
||||
```ludic
|
||||
program ReadConfig {
|
||||
handler ShowConfig phase Start {
|
||||
let level = getenv("LUDIC_LEVEL")
|
||||
if len(level) == 0 {
|
||||
print("default")
|
||||
} else {
|
||||
print(level)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,8 +5,23 @@ category: builtins
|
|||
kind: builtin
|
||||
tokens: len
|
||||
sig: len(x) -> int
|
||||
tip: Length of a slice or string.
|
||||
tip: The number of elements in a slice, or the byte length of a string.
|
||||
order: 5
|
||||
---
|
||||
|
||||
Length of a slice or string.
|
||||
Returns the length of its argument: the element count of a slice, or the byte length of a string. Use it to iterate a slice with <code>for index in 0 .. len(items)</code>, to check whether a collection is empty, or to compute string slice bounds such as trimming a suffix. It reads the current length, so after you <code>push</code> onto a slice the value it returns grows accordingly. For strings, note it counts bytes, not visual characters.
|
||||
|
||||
Parameters:
|
||||
- `x` — a slice or string to measure
|
||||
|
||||
```ludic
|
||||
program CountEnemies {
|
||||
handler ReportCount phase Start {
|
||||
let wave_sizes = new []int
|
||||
push(wave_sizes, 3)
|
||||
push(wave_sizes, 5)
|
||||
push(wave_sizes, 8)
|
||||
print(len(wave_sizes))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,8 +5,32 @@ category: builtins
|
|||
kind: builtin
|
||||
tokens: load
|
||||
sig: load() -> bool
|
||||
tip: Restore a snapshot written by save().
|
||||
tip: Restore the world from a snapshot previously written by save().
|
||||
order: 4
|
||||
---
|
||||
|
||||
Restore a snapshot written by <code>save()</code>.
|
||||
Restores the entire world from the snapshot most recently written by <code>save()</code> — every model instance, property, and program <code>var</code> returns to its saved state. It returns a <code>bool</code>: <code>true</code> when a snapshot existed and was restored, <code>false</code> when there was nothing to load, so you can guard the call and avoid clobbering the current world by mistake. Use it for quick-load, respawning at a checkpoint, or resetting a test to a known state. It takes no arguments.
|
||||
|
||||
```ludic
|
||||
program QuickLoad {
|
||||
property Position { column: int = 0, row: int = 0 }
|
||||
model Player { Position }
|
||||
|
||||
handler SpawnPlayer phase Start {
|
||||
spawn Player { Position { column: 5, row: 5 } }
|
||||
save()
|
||||
}
|
||||
|
||||
handler ReadInput phase Input {
|
||||
if Input.key() == 'r' {
|
||||
let restored = load()
|
||||
if restored { print(1) }
|
||||
}
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
35
docs/language/builtins/fn-max.md
Normal file
35
docs/language/builtins/fn-max.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
id: fn-max
|
||||
name: max
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: max
|
||||
sig: max(a, b) -> int
|
||||
tip: The larger of two integers.
|
||||
order: 50
|
||||
---
|
||||
|
||||
Returns whichever of its two integer arguments is larger. Use it to enforce a lower bound (for example clamping a countdown so it never drops below zero with <code>max(remaining, 0)</code>), to track a running high score, or to pick the greater of two candidate values. For the smaller of two values use <code>min</code>, and to bound a value on both sides at once use <code>clamp</code>.
|
||||
|
||||
Parameters:
|
||||
- `a` — the first value
|
||||
- `b` — the second value
|
||||
|
||||
```ludic
|
||||
program HighScore {
|
||||
var score: int = 0
|
||||
var best_score: int = 0
|
||||
|
||||
handler EarnPoints phase Update {
|
||||
score = score + 5
|
||||
best_score = max(best_score, score)
|
||||
if score >= 20 { quit() }
|
||||
}
|
||||
|
||||
handler ReportBest phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
Screen.draw_number(x: 8, y: 8, value: best_score, color: Color.Gold, scale: 1)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -1,12 +1,41 @@
|
|||
---
|
||||
id: fn-min
|
||||
name: min / max
|
||||
name: min
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: min max
|
||||
sig: min(a, b) / max(a, b) -> int
|
||||
tip: The smaller / larger of two ints.
|
||||
order: 7
|
||||
tokens: min
|
||||
sig: min(a, b) -> int
|
||||
tip: The smaller of two integers.
|
||||
order: 50
|
||||
---
|
||||
|
||||
The smaller / larger of two ints.
|
||||
Returns whichever of its two integer arguments is smaller. Use it to enforce an upper bound — for instance capping healing so it never exceeds a maximum with <code>min(current + heal, maximum)</code> — or to pick the lesser of two candidate values. For the larger of two values use <code>max</code>, and to bound a value between a low and a high at once use <code>clamp</code>.
|
||||
|
||||
Parameters:
|
||||
- `a` — the first value
|
||||
- `b` — the second value
|
||||
|
||||
```ludic
|
||||
program CappedHealing {
|
||||
property Health { current: int = 100, maximum: int = 100 }
|
||||
model Player { Health }
|
||||
|
||||
handler SpawnPlayer phase Start {
|
||||
spawn Player { Health { current: 90, maximum: 100 } }
|
||||
}
|
||||
|
||||
handler ApplyHeal phase Update {
|
||||
for (Health) in query [Health, {Player}] {
|
||||
Health.current = min(Health.current + 25, Health.maximum)
|
||||
}
|
||||
}
|
||||
|
||||
handler ReportHealth phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
for (Health) in query [Health, {Player}] {
|
||||
Screen.draw_number(x: 8, y: 8, value: Health.current, color: Color.LimeGreen, scale: 1)
|
||||
}
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,8 +5,23 @@ category: builtins
|
|||
kind: builtin
|
||||
tokens: print
|
||||
sig: print(x)
|
||||
tip: Print an int or string, followed by a newline — for headless tests and debugging.
|
||||
tip: Print an int or string followed by a newline — for headless tests and debugging.
|
||||
order: 0
|
||||
---
|
||||
|
||||
Print an int or string, followed by a newline — for headless tests and debugging.
|
||||
Writes its argument to standard output followed by a newline. It accepts either an integer or a string, so it is the go-to tool for quick debugging and for headless test programs that emit numbers a test harness can check. It is a diagnostic channel, separate from anything drawn on screen with the <code>Screen</code> API, and is most useful in <code>Start</code>- or <code>Update</code>-phase handlers while you are iterating. For interpolated messages, build the string first with a backtick template and pass that.
|
||||
|
||||
Parameters:
|
||||
- `x` — the value to print, an `int` or a string
|
||||
|
||||
```ludic
|
||||
program PrintScore {
|
||||
var score: int = 0
|
||||
|
||||
handler CountUp phase Update {
|
||||
score = score + 10
|
||||
print(score)
|
||||
if score >= 30 { quit() }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,8 +5,25 @@ category: builtins
|
|||
kind: builtin
|
||||
tokens: push
|
||||
sig: push(slice, x)
|
||||
tip: Append to a slice.
|
||||
tip: Append one element to the end of a growable slice.
|
||||
order: 6
|
||||
---
|
||||
|
||||
Append to a slice.
|
||||
Appends a value to the end of a slice, growing it by one element. Create the slice first with <code>new []T</code>, then <code>push</code> items onto it; afterward <code>len</code> reflects the new count and the element is reachable by index. Use it to build up lists at runtime — a queue of spawn requests, collected scores, parsed tokens — where you don't know the size in advance. The value's type must match the slice's element type.
|
||||
|
||||
Parameters:
|
||||
- `slice` — the growable slice to append to
|
||||
- `x` — the element to append
|
||||
|
||||
```ludic
|
||||
program BuildWaveList {
|
||||
handler PlanWaves phase Start {
|
||||
let wave_sizes = new []int
|
||||
push(wave_sizes, 4)
|
||||
push(wave_sizes, 6)
|
||||
for index in 0 .. len(wave_sizes) {
|
||||
print(wave_sizes[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,8 +5,22 @@ category: builtins
|
|||
kind: builtin
|
||||
tokens: quit
|
||||
sig: quit()
|
||||
tip: Stop the game loop after this frame.
|
||||
tip: Stop the game loop cleanly after the current frame finishes.
|
||||
order: 2
|
||||
---
|
||||
|
||||
Stop the game loop after this frame.
|
||||
Signals the runtime to stop the game loop after the current frame completes, ending the program in an orderly way. Because it lets the in-progress frame finish, any drawing you have already queued for this frame is still presented. Use it for a "quit to desktop" action, to end a headless test once its work is done, or to stop on a win/lose condition. For an immediate, mid-frame stop with a status code instead, use <code>exit</code>. It takes no arguments.
|
||||
|
||||
```ludic
|
||||
program QuitOnKey {
|
||||
handler ReadInput phase Input {
|
||||
if Input.key() == 'q' { quit() }
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
Screen.draw_text(x: 8, y: 8, text: "PRESS Q TO QUIT", color: Color.White, scale: 1)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,8 +5,22 @@ category: builtins
|
|||
kind: builtin
|
||||
tokens: read_char
|
||||
sig: read_char() -> int
|
||||
tip: Read one byte from standard input (-1 at end of input).
|
||||
tip: Read one byte from standard input, or -1 at end of input.
|
||||
order: 24
|
||||
---
|
||||
|
||||
Read a single byte from standard input, returning its value or <code>-1</code> at end of input — the basis for headless, pipe-driven runs.
|
||||
Reads a single byte from standard input and returns its value, or <code>-1</code> when the input has ended. It is the foundation for headless, pipe-driven runs: a test or tool can feed the program a stream of bytes and process them one at a time, looping until <code>read_char</code> returns <code>-1</code>. Compare the byte against character literals such as <code>'a'</code> to interpret it. It takes no arguments and advances through the input on each call.
|
||||
|
||||
```ludic
|
||||
program CountInputBytes {
|
||||
handler DrainInput phase Start {
|
||||
var byte_count: int = 0
|
||||
var next_byte = read_char()
|
||||
while next_byte != -1 {
|
||||
byte_count = byte_count + 1
|
||||
next_byte = read_char()
|
||||
}
|
||||
print(byte_count)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,8 +5,20 @@ category: builtins
|
|||
kind: builtin
|
||||
tokens: run
|
||||
sig: run(cmd)
|
||||
tip: Run a shell command.
|
||||
tip: Run a string as a shell command.
|
||||
order: 22
|
||||
---
|
||||
|
||||
Run <code>cmd</code> as a shell command — handy for build steps and tooling written in Ludic.
|
||||
Executes its string argument as a shell command on the host. It exists so build steps and developer tooling can be written in Ludic itself — invoking the compiler, moving files, or chaining tools — rather than in a separate shell script. It is a tooling and scripting facility, not something a shipped game loop would normally use. Treat the command string carefully, since it runs with the same privileges as the program.
|
||||
|
||||
Parameters:
|
||||
- `cmd` — the shell command line to run
|
||||
|
||||
```ludic
|
||||
program BuildStep {
|
||||
handler RunTool phase Start {
|
||||
run("mkdir -p build")
|
||||
run("echo built")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,8 +5,30 @@ category: builtins
|
|||
kind: builtin
|
||||
tokens: save
|
||||
sig: save()
|
||||
tip: Serialize the entire world — every entity, property and program var — to a snapshot in one call.
|
||||
tip: Serialize the whole world — every model instance, property, and program var — in one call.
|
||||
order: 3
|
||||
---
|
||||
|
||||
Serialize the entire world — every entity, property and program <code>var</code> — to a snapshot in one call.
|
||||
Captures a complete snapshot of the running world in a single call: every spawned model instance and its properties, plus every program-level <code>var</code>. The compiler generates the serialization for you from your declarations, so you never write it by hand. Pair it with <code>load</code> to implement quick-save / quick-load, checkpoints, or deterministic test fixtures. It takes no arguments and writes to the runtime's snapshot slot; calling it again overwrites the previous snapshot.
|
||||
|
||||
```ludic
|
||||
program QuickSave {
|
||||
property Position { column: int = 0, row: int = 0 }
|
||||
model Player { Position }
|
||||
|
||||
handler SpawnPlayer phase Start {
|
||||
spawn Player { Position { column: 5, row: 5 } }
|
||||
}
|
||||
|
||||
handler ReadInput phase Input {
|
||||
let pressed = Input.key()
|
||||
if pressed == 's' { save() }
|
||||
if pressed == 'l' { load() }
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,8 +5,22 @@ category: builtins
|
|||
kind: builtin
|
||||
tokens: str
|
||||
sig: str(x) -> str
|
||||
tip: Convert an int/bool/fixed to text; a string passes through.
|
||||
tip: Convert an int, bool, or fixed value to text; a string passes through unchanged.
|
||||
order: 1
|
||||
---
|
||||
|
||||
Convert an int/bool/fixed to text; a string passes through. Also used by <code>`{…}`</code> interpolation.
|
||||
Converts a value to its textual form: an <code>int</code>, <code>bool</code>, or <code>fixed</code> becomes a string, and a value that is already a string is returned unchanged. This is what backtick <code>`{…}`</code> interpolation calls under the hood, so most of the time you can interpolate directly instead of calling <code>str</code> yourself. Reach for the explicit form when you need to store or pass around the text, or build a string in pieces. The result can be printed, drawn with <code>Screen.draw_text</code>, or concatenated.
|
||||
|
||||
Parameters:
|
||||
- `x` — the value to convert to text
|
||||
|
||||
```ludic
|
||||
program LabelValue {
|
||||
var score: int = 250
|
||||
|
||||
handler ReportScore phase Start {
|
||||
let label = str(score)
|
||||
print(label)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
43
docs/language/builtins/fn-ui_build.md
Normal file
43
docs/language/builtins/fn-ui_build.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
id: fn-ui_build
|
||||
name: ui_build
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: ui_build
|
||||
sig: ui_build()
|
||||
tip: Build the declared UI tree so it can be opened and rendered.
|
||||
order: 50
|
||||
---
|
||||
|
||||
Constructs the retained-UI tree you declared in a <code>ui</code> block, laying out its panels, labels, and buttons and loading any skins or images they reference. Call it once during a <code>Start</code>-phase handler, after loading any fonts the UI needs, and before you open a screen with <code>ui_open</code> or draw it with <code>ui_render</code>. Because the UI is declared as data and built in one step, the game code only has to build it, open it, and read clicks. It takes no arguments.
|
||||
|
||||
```ludic
|
||||
program TitleMenu {
|
||||
var title_font: int = 0
|
||||
|
||||
ui MainMenu {
|
||||
panel id: Root w: 240 pad: 16 gap: 6 bg: 0x1a1a2c align: center {
|
||||
label text: "CHRONORIFT" font: title_font size: 24 fg: 0xffe060 align: center
|
||||
button id: NewGame text: "New Game" font: title_font size: 16 w: 200
|
||||
button id: Quit text: "Quit" font: title_font size: 16 w: 200
|
||||
}
|
||||
}
|
||||
|
||||
handler Boot phase Start {
|
||||
title_font = font_load("/System/Library/Fonts/Supplemental/Arial.ttf")
|
||||
ui_build()
|
||||
ui_open(UI_MainMenu)
|
||||
}
|
||||
|
||||
handler Navigate phase Update {
|
||||
ui_tick(Input.key())
|
||||
if ui_clicked(UI_Quit) { quit() }
|
||||
}
|
||||
|
||||
handler DrawWorld phase Render {
|
||||
Screen.clear(Color.MidnightBlue)
|
||||
ui_render()
|
||||
Screen.show()
|
||||
}
|
||||
}
|
||||
```
|
||||
31
docs/language/builtins/fn-words.md
Normal file
31
docs/language/builtins/fn-words.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
id: fn-words
|
||||
name: words
|
||||
category: builtins
|
||||
kind: builtin
|
||||
tokens: words
|
||||
sig: words(n) -> words
|
||||
tip: Allocate a raw buffer of n 32-bit words, indexable with [i].
|
||||
order: 50
|
||||
---
|
||||
|
||||
Allocates a raw buffer of <code>n</code> 32-bit words and returns a <code>words</code> value you can index with <code>buffer[i]</code> to read or write each word as an <code>int</code>. Use it when you want a flat, fixed-size numeric scratch array — a lookup table, a small ring buffer, or per-slot counters — without declaring a model. It is lower-level than a growable <code>[]int</code> slice: the size is chosen up front and there is no <code>push</code>. For byte-granular storage use <code>bytes</code> instead.
|
||||
|
||||
Parameters:
|
||||
- `n` — the number of 32-bit words to allocate
|
||||
|
||||
```ludic
|
||||
program WordScratch {
|
||||
handler SumScores phase Start {
|
||||
let scores = words(3)
|
||||
scores[0] = 10
|
||||
scores[1] = 20
|
||||
scores[2] = 30
|
||||
var total: int = 0
|
||||
for index in 0 .. 3 {
|
||||
total = total + scores[index]
|
||||
}
|
||||
print(total)
|
||||
}
|
||||
}
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue