feat(stdlib): namespaced standard library (issue #2)
Implement the bulk of the namespaced-stdlib proposal (workshopsoft/ludic#2): 156 namespace methods across Math, Text, List, Ease, Collide, World, Net, Sys, Save, Mem, extended Screen, Color functions, extended Random, and Time. All deterministic fixed-point; self-hosting (C-free bootstrap fixpoint holds). Compiler (selfhost/): - Math.*: sqrt/sin/cos/tan/atan2/asin/acos (fixed-point runtime prelude — bit-by-bit isqrt, 256-entry interpolated sine table, Ross atan2), plus hypot/dist/dist2/deg_to_rad/rad_to_deg/posmod/wrap/ping_pong/snapped/ move_toward/smoothstep/lerp/remap/sign/floor/ceil/round. - Text.* (complete): upper/lower/trim/repeat/pad, split/join/replace, and the libc-backed queries. - List.* (complete): insert/remove_at/remove/sort plus the earlier ops. - Ease.* (in/out/in_out/back/bounce) and Collide.* (rects/point_rect/ circles/rect_circle). - Phase 3: World/Net/Sys/Save namespaced over the bare builtins (byte- identical IR) and Mem.* (bytes/words/copy/fill/peek/poke). - Screen.* extended (line/circle/fill_circle/triangle/fill_triangle via new runtime primitives; sprite/sprite_scaled aliases), Color.* functions, Random.* (value/int/sign), Time.* (frame/delta/elapsed/now — new game-loop frame counter). - Fix a lexer bug: fixed-point literals with >4 fractional digits overflowed. Docs & tooling: - 129 new per-symbol doc pages; gen.py made data-driven (namespaces discovered from the docs, no hardcoded list); new check-impl.py enforces that every implemented namespace method / keyword / type / phase has a doc page, wired into `x test-tools`. Document the previously-undocumented keywords (break/continue/where/entry/new/public + and/or/not tokens). - LSP: namespaced signature help (ns_method_sig) covering every namespace. Tests: 12 new self-host/regression tests + a golden render for the drawing primitives. All suites green (selfhost 21, regression 45, tools 29). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ff15c4e01d
commit
a38195128f
235 changed files with 24676 additions and 7762 deletions
7
docs/language/math/_section.md
Normal file
7
docs/language/math/_section.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
id: math
|
||||
title: Math
|
||||
order: 6
|
||||
---
|
||||
|
||||
Deterministic fixed-point math. Every function is computed in Q16.16 with plain integer arithmetic, so results are bit-identical on every platform and every run — the same guarantee the rest of the runtime gives. Arguments are positional.
|
||||
28
docs/language/math/math-abs.md
Normal file
28
docs/language/math/math-abs.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
id: math-abs
|
||||
name: Math.abs
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.abs
|
||||
sig: Math.abs(x) -> int
|
||||
tip: The magnitude of a value, dropping its sign.
|
||||
order: 2
|
||||
ns: Math
|
||||
member: abs
|
||||
---
|
||||
|
||||
Returns the absolute value of <code>x</code> — its distance from zero, always non-negative. It preserves the type of its argument, so <code>Math.abs</code> of a <code>fixed</code> stays <code>fixed</code>. Reach for it when you care about how far apart two things are regardless of direction, such as the gap between a target and current position, or the speed behind a signed velocity. The bare form <code>abs(x)</code> is kept as a convenience alias.
|
||||
|
||||
Parameters:
|
||||
- `x` — the value whose magnitude you want
|
||||
|
||||
```ludic
|
||||
program ChaseDistance {
|
||||
var target: int = 40
|
||||
var here: int = 12
|
||||
|
||||
handler Report phase Update {
|
||||
let gap = Math.abs(target - here) # 28, regardless of which is larger
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-acos.md
Normal file
22
docs/language/math/math-acos.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-acos
|
||||
name: Math.acos
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.acos
|
||||
sig: Math.acos(x) -> fixed
|
||||
tip: The arccosine of a value, in radians.
|
||||
order: 28
|
||||
ns: Math
|
||||
member: acos
|
||||
---
|
||||
|
||||
Returns the angle in radians whose cosine is <code>x</code>, in <code>0</code>..<code>pi</code>. The input should be in <code>-1.0</code>..<code>1.0</code>. Handy for recovering the angle between two normalized directions from their dot product.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let between = Math.acos(dot)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-asin.md
Normal file
22
docs/language/math/math-asin.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-asin
|
||||
name: Math.asin
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.asin
|
||||
sig: Math.asin(x) -> fixed
|
||||
tip: The arcsine of a value, in radians.
|
||||
order: 27
|
||||
ns: Math
|
||||
member: asin
|
||||
---
|
||||
|
||||
Returns the angle in radians whose sine is <code>x</code>, in <code>-pi/2</code>..<code>pi/2</code>. The input should be in <code>-1.0</code>..<code>1.0</code>. Built from <code>Math.atan2</code> and <code>Math.sqrt</code>, so it is deterministic.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let angle = Math.asin(height / radius)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-atan2.md
Normal file
22
docs/language/math/math-atan2.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-atan2
|
||||
name: Math.atan2
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.atan2
|
||||
sig: Math.atan2(y, x) -> fixed
|
||||
tip: The angle of the vector (x, y), in radians.
|
||||
order: 26
|
||||
ns: Math
|
||||
member: atan2
|
||||
---
|
||||
|
||||
Returns the angle in radians from the positive x-axis to the point <code>(x, y)</code>, in the full range <code>-pi</code>..<code>pi</code> — the standard way to get a heading from a direction vector. Note the argument order is <code>y</code> then <code>x</code>. Deterministic fixed-point, accurate to within about a degree.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let heading = Math.atan2(target_y - y, target_x - x)
|
||||
}
|
||||
}
|
||||
```
|
||||
29
docs/language/math/math-ceil.md
Normal file
29
docs/language/math/math-ceil.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
id: math-ceil
|
||||
name: Math.ceil
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.ceil
|
||||
sig: Math.ceil(x) -> int
|
||||
tip: Round a fixed value up to the nearest whole int.
|
||||
order: 6
|
||||
ns: Math
|
||||
member: ceil
|
||||
---
|
||||
|
||||
Takes a <code>fixed</code> value and returns the smallest <code>int</code> not less than it — rounding toward positive infinity, so <code>Math.ceil(2.1)</code> is <code>3</code> and <code>Math.ceil(4.0)</code> stays <code>4</code>. Use it when you need to cover a fractional amount with whole units: how many full tiles a span touches, or how many rows a variable-height list needs.
|
||||
|
||||
Parameters:
|
||||
- `x` — the `fixed` value to round up
|
||||
|
||||
```ludic
|
||||
program RowsNeeded {
|
||||
const ROW_HEIGHT: fixed = 12.0
|
||||
|
||||
var content_height: fixed = 30.0
|
||||
|
||||
handler Layout phase Update {
|
||||
let rows = Math.ceil(content_height / ROW_HEIGHT) # 3
|
||||
}
|
||||
}
|
||||
```
|
||||
31
docs/language/math/math-clamp.md
Normal file
31
docs/language/math/math-clamp.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
id: math-clamp
|
||||
name: Math.clamp
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.clamp
|
||||
sig: Math.clamp(v, lo, hi) -> int
|
||||
tip: Constrain a value to the range [lo, hi].
|
||||
order: 3
|
||||
ns: Math
|
||||
member: clamp
|
||||
---
|
||||
|
||||
Returns <code>v</code> held inside the inclusive range <code>lo</code>..<code>hi</code>: below <code>lo</code> it returns <code>lo</code>, above <code>hi</code> it returns <code>hi</code>, otherwise <code>v</code> unchanged. It is the single-call form of <code>Math.max(lo, Math.min(v, hi))</code> and works for <code>int</code> and <code>fixed</code> alike. Use it to keep a camera inside the world, a slider within its track, or a stat within its legal bounds. The bare form <code>clamp(v, lo, hi)</code> is kept as a convenience alias.
|
||||
|
||||
Parameters:
|
||||
- `v` — the value to constrain
|
||||
- `lo` — the lowest allowed result (inclusive)
|
||||
- `hi` — the highest allowed result (inclusive)
|
||||
|
||||
```ludic
|
||||
program CameraBounds {
|
||||
const WORLD_WIDTH: int = 320
|
||||
|
||||
var camera_x: int = 0
|
||||
|
||||
handler Follow phase Update {
|
||||
camera_x = Math.clamp(camera_x, 0, WORLD_WIDTH - 1)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-cos.md
Normal file
22
docs/language/math/math-cos.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-cos
|
||||
name: Math.cos
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.cos
|
||||
sig: Math.cos(radians) -> fixed
|
||||
tip: Cosine of an angle in radians.
|
||||
order: 13
|
||||
ns: Math
|
||||
member: cos
|
||||
---
|
||||
|
||||
Returns the cosine of <code>radians</code> as a <code>fixed</code> in <code>-1.0</code>..<code>1.0</code>, computed as <code>Math.sin(radians + pi/2)</code> off the same deterministic table. Use <code>Math.cos</code> and <code>Math.sin</code> together to convert an angle into an <code>(x, y)</code> direction — for orbits, aiming, or steering.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let x = center_x + Math.cos(angle) * radius
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-deg_to_rad.md
Normal file
22
docs/language/math/math-deg_to_rad.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-deg_to_rad
|
||||
name: Math.deg_to_rad
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.deg_to_rad
|
||||
sig: Math.deg_to_rad(degrees) -> fixed
|
||||
tip: Convert degrees to radians.
|
||||
order: 18
|
||||
ns: Math
|
||||
member: deg_to_rad
|
||||
---
|
||||
|
||||
Converts an angle from degrees to radians (multiplying by <code>pi/180</code>), since <code>Math.sin</code>/<code>Math.cos</code>/<code>Math.tan</code> all take radians. Use it when your data or design speaks in degrees — a 90-degree turn, a 45-degree cone — and you need to feed it to the trig functions.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let r = Math.sin(Math.deg_to_rad(90.0)) # ~1.0
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-dist.md
Normal file
22
docs/language/math/math-dist.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-dist
|
||||
name: Math.dist
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.dist
|
||||
sig: Math.dist(x0, y0, x1, y1) -> fixed
|
||||
tip: Distance between two points.
|
||||
order: 16
|
||||
ns: Math
|
||||
member: dist
|
||||
---
|
||||
|
||||
Returns the straight-line distance between <code>(x0, y0)</code> and <code>(x1, y1)</code> — <code>sqrt(dx*dx + dy*dy)</code> in fixed-point. Use it for proximity and range checks. When you only need to compare distances (which is nearer? within radius?), prefer <code>Math.dist2</code>, which skips the square root.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
if Math.dist(px, py, ex, ey) < attack_range { strike() }
|
||||
}
|
||||
}
|
||||
```
|
||||
23
docs/language/math/math-dist2.md
Normal file
23
docs/language/math/math-dist2.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
id: math-dist2
|
||||
name: Math.dist2
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.dist2
|
||||
sig: Math.dist2(x0, y0, x1, y1) -> fixed
|
||||
tip: Squared distance between two points.
|
||||
order: 17
|
||||
ns: Math
|
||||
member: dist2
|
||||
---
|
||||
|
||||
Returns the squared distance between <code>(x0, y0)</code> and <code>(x1, y1)</code> — <code>dx*dx + dy*dy</code>, with no square root. Comparing squared distances gives the same ordering as comparing real distances, so this is the cheaper choice for "nearest" and "within radius" tests: compare against <code>radius * radius</code> instead of calling <code>Math.dist</code>.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let r2 = radius * radius
|
||||
if Math.dist2(px, py, ex, ey) < r2 { in_range() }
|
||||
}
|
||||
}
|
||||
```
|
||||
29
docs/language/math/math-floor.md
Normal file
29
docs/language/math/math-floor.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
id: math-floor
|
||||
name: Math.floor
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.floor
|
||||
sig: Math.floor(x) -> int
|
||||
tip: Round a fixed value down to the nearest whole int.
|
||||
order: 5
|
||||
ns: Math
|
||||
member: floor
|
||||
---
|
||||
|
||||
Takes a <code>fixed</code> value and returns the largest <code>int</code> not greater than it — rounding toward negative infinity, so <code>Math.floor(2.7)</code> is <code>2</code> and <code>Math.floor(-0.2)</code> is <code>-1</code>. This is the fixed-to-int conversion you reach for when turning a smooth position into a whole tile or pixel index. It is the same operation as the bare <code>flr(x)</code>.
|
||||
|
||||
Parameters:
|
||||
- `x` — the `fixed` value to round down
|
||||
|
||||
```ludic
|
||||
program TileIndex {
|
||||
const TILE_SIZE: fixed = 16.0
|
||||
|
||||
var world_x: fixed = 40.5
|
||||
|
||||
handler Locate phase Update {
|
||||
let column = Math.floor(world_x / TILE_SIZE) # 2
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-hypot.md
Normal file
22
docs/language/math/math-hypot.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-hypot
|
||||
name: Math.hypot
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.hypot
|
||||
sig: Math.hypot(x, y) -> fixed
|
||||
tip: Length of the vector (x, y).
|
||||
order: 15
|
||||
ns: Math
|
||||
member: hypot
|
||||
---
|
||||
|
||||
Returns <code>sqrt(x*x + y*y)</code> — the length of the 2D vector <code>(x, y)</code>, or equivalently the hypotenuse of a right triangle with those legs. It is the one-call form for a magnitude and reads more clearly than spelling out the squares and root. To normalize a vector, divide each component by its <code>Math.hypot</code>.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let length = Math.hypot(velocity_x, velocity_y)
|
||||
}
|
||||
}
|
||||
```
|
||||
31
docs/language/math/math-inverse_lerp.md
Normal file
31
docs/language/math/math-inverse_lerp.md
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
id: math-inverse_lerp
|
||||
name: Math.inverse_lerp
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.inverse_lerp
|
||||
sig: Math.inverse_lerp(a, b, v) -> fixed
|
||||
tip: Find where a value sits between two endpoints as a 0..1 fraction.
|
||||
order: 9
|
||||
ns: Math
|
||||
member: inverse_lerp
|
||||
---
|
||||
|
||||
The inverse of <code>Math.lerp</code>: given endpoints <code>a</code> and <code>b</code> and a value <code>v</code>, it returns the fraction <code>(v - a) / (b - a)</code> — <code>0.0</code> when <code>v</code> equals <code>a</code>, <code>1.0</code> when it equals <code>b</code>. Use it to turn a raw quantity into a normalized amount: how full a health bar is, how far a timer has run, or the <code>t</code> to feed into another <code>Math.lerp</code>. The endpoints must differ, since equal ones would divide by zero.
|
||||
|
||||
Parameters:
|
||||
- `a` — the endpoint that maps to `0.0`
|
||||
- `b` — the endpoint that maps to `1.0`
|
||||
- `v` — the value to locate between them
|
||||
|
||||
```ludic
|
||||
program HealthFraction {
|
||||
const MAX_HP: fixed = 200.0
|
||||
|
||||
var hp: fixed = 150.0
|
||||
|
||||
handler Draw phase Render {
|
||||
let fraction = Math.inverse_lerp(0.0, MAX_HP, hp) # 0.75
|
||||
}
|
||||
}
|
||||
```
|
||||
30
docs/language/math/math-lerp.md
Normal file
30
docs/language/math/math-lerp.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
id: math-lerp
|
||||
name: Math.lerp
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.lerp
|
||||
sig: Math.lerp(a, b, t) -> fixed
|
||||
tip: Blend between two values by a 0..1 amount.
|
||||
order: 8
|
||||
ns: Math
|
||||
member: lerp
|
||||
---
|
||||
|
||||
Linear interpolation: returns <code>a</code> when <code>t</code> is <code>0.0</code>, <code>b</code> when <code>t</code> is <code>1.0</code>, and a proportional blend in between — the exact value <code>a + (b - a) * t</code>. All three arguments are <code>fixed</code> and the result is <code>fixed</code>. It is the workhorse behind smooth motion: ease a camera toward a target, fade a value over time, or sample a gradient. Values of <code>t</code> outside <code>0..1</code> extrapolate past the endpoints.
|
||||
|
||||
Parameters:
|
||||
- `a` — the value returned at `t = 0.0`
|
||||
- `b` — the value returned at `t = 1.0`
|
||||
- `t` — the blend amount, normally `0.0`..`1.0`
|
||||
|
||||
```ludic
|
||||
program EaseCamera {
|
||||
var camera_x: fixed = 0.0
|
||||
var target_x: fixed = 100.0
|
||||
|
||||
handler Follow phase Update {
|
||||
camera_x = Math.lerp(camera_x, target_x, 0.1) # glides a tenth of the way each frame
|
||||
}
|
||||
}
|
||||
```
|
||||
28
docs/language/math/math-max.md
Normal file
28
docs/language/math/math-max.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
id: math-max
|
||||
name: Math.max
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.max
|
||||
sig: Math.max(a, b) -> int
|
||||
tip: The larger of two values.
|
||||
order: 1
|
||||
ns: Math
|
||||
member: max
|
||||
---
|
||||
|
||||
Returns whichever of <code>a</code> and <code>b</code> is larger. Like <code>Math.min</code>, it works for both <code>int</code> and <code>fixed</code> operands. Use it to hold a value at a floor — for example keeping a countdown at zero instead of going negative, or a health bar from dropping below the minimum. The bare form <code>max(a, b)</code> is kept as a convenience alias.
|
||||
|
||||
Parameters:
|
||||
- `a` — the first value
|
||||
- `b` — the second value
|
||||
|
||||
```ludic
|
||||
program Countdown {
|
||||
var timer: int = 60
|
||||
|
||||
handler Tick phase Update {
|
||||
timer = Math.max(timer - 1, 0) # stops at 0, never negative
|
||||
}
|
||||
}
|
||||
```
|
||||
28
docs/language/math/math-min.md
Normal file
28
docs/language/math/math-min.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
id: math-min
|
||||
name: Math.min
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.min
|
||||
sig: Math.min(a, b) -> int
|
||||
tip: The smaller of two values.
|
||||
order: 0
|
||||
ns: Math
|
||||
member: min
|
||||
---
|
||||
|
||||
Returns whichever of <code>a</code> and <code>b</code> is smaller. It works on plain <code>int</code> values and on <code>fixed</code> values alike — both are stored as signed 32-bit words, so the comparison keeps their order either way. Use it to cap a value at a ceiling, keep a cursor inside a list, or take the nearer of two distances. The bare form <code>min(a, b)</code> is kept as a convenience alias.
|
||||
|
||||
Parameters:
|
||||
- `a` — the first value
|
||||
- `b` — the second value
|
||||
|
||||
```ludic
|
||||
program ClampScore {
|
||||
var score: int = 0
|
||||
|
||||
handler AddPoint phase Update {
|
||||
score = Math.min(score + 1, 100) # never climbs past 100
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-move_toward.md
Normal file
22
docs/language/math/math-move_toward.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-move_toward
|
||||
name: Math.move_toward
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.move_toward
|
||||
sig: Math.move_toward(from, to, delta) -> fixed
|
||||
tip: Step from one value toward another by at most delta.
|
||||
order: 24
|
||||
ns: Math
|
||||
member: move_toward
|
||||
---
|
||||
|
||||
Moves <code>from</code> toward <code>to</code> by at most <code>delta</code>, never overshooting — when the gap is smaller than <code>delta</code> it returns exactly <code>to</code>. Unlike <code>Math.lerp</code>, which eases by a fraction of the remaining distance, this moves at a constant rate, which is what you want for turrets, meters, and UI values that should approach a target steadily and then stop.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
health_shown = Math.move_toward(health_shown, health, 2.0)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-ping_pong.md
Normal file
22
docs/language/math/math-ping_pong.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-ping_pong
|
||||
name: Math.ping_pong
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.ping_pong
|
||||
sig: Math.ping_pong(t, len) -> int
|
||||
tip: Bounce a counter back and forth in [0, len].
|
||||
order: 22
|
||||
ns: Math
|
||||
member: ping_pong
|
||||
---
|
||||
|
||||
Maps an ever-increasing counter <code>t</code> to a value that rises from <code>0</code> to <code>len</code> and back, bouncing forever — the triangle wave to <code>Math.wrap</code>'s sawtooth. Feed it a frame counter to drive a patrol, a pulsing highlight, or a back-and-forth sweep without tracking direction yourself.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let phase = Math.ping_pong(frame, 30)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-posmod.md
Normal file
22
docs/language/math/math-posmod.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-posmod
|
||||
name: Math.posmod
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.posmod
|
||||
sig: Math.posmod(a, m) -> int
|
||||
tip: Modulo that is always non-negative.
|
||||
order: 20
|
||||
ns: Math
|
||||
member: posmod
|
||||
---
|
||||
|
||||
Returns <code>a</code> modulo <code>m</code>, but always in the range <code>0</code>..<code>m-1</code> — unlike the <code>%</code> operator, which keeps the sign of <code>a</code> (so <code>-1 % 5</code> is <code>-1</code>, while <code>Math.posmod(-1, 5)</code> is <code>4</code>). It is what you want for wrapping indices and grid coordinates that can go negative.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let col = Math.posmod(x, grid_width) # never negative
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-rad_to_deg.md
Normal file
22
docs/language/math/math-rad_to_deg.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-rad_to_deg
|
||||
name: Math.rad_to_deg
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.rad_to_deg
|
||||
sig: Math.rad_to_deg(radians) -> fixed
|
||||
tip: Convert radians to degrees.
|
||||
order: 19
|
||||
ns: Math
|
||||
member: rad_to_deg
|
||||
---
|
||||
|
||||
Converts an angle from radians to degrees (multiplying by <code>180/pi</code>) — the inverse of <code>Math.deg_to_rad</code>. Use it to present an internally-radians angle in the degrees a player or designer expects, for a HUD readout or a debug overlay.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let heading_deg = Math.rad_to_deg(heading)
|
||||
}
|
||||
}
|
||||
```
|
||||
34
docs/language/math/math-remap.md
Normal file
34
docs/language/math/math-remap.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
id: math-remap
|
||||
name: Math.remap
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.remap
|
||||
sig: Math.remap(v, in0, in1, out0, out1) -> fixed
|
||||
tip: Rescale a value from one range into another.
|
||||
order: 10
|
||||
ns: Math
|
||||
member: remap
|
||||
---
|
||||
|
||||
Maps <code>v</code> from the input range <code>in0</code>..<code>in1</code> onto the output range <code>out0</code>..<code>out1</code>, keeping its relative position — equivalent to <code>Math.lerp(out0, out1, Math.inverse_lerp(in0, in1, v))</code>. All arguments are <code>fixed</code>. It is the one call that converts between units: a health value into a bar width, a noise sample in <code>-1..1</code> into a <code>0..255</code> shade, or a mouse position into a world coordinate. Swap the output ends to invert the mapping.
|
||||
|
||||
Parameters:
|
||||
- `v` — the value to rescale
|
||||
- `in0` — the start of the input range
|
||||
- `in1` — the end of the input range
|
||||
- `out0` — the start of the output range
|
||||
- `out1` — the end of the output range
|
||||
|
||||
```ludic
|
||||
program HealthBar {
|
||||
const MAX_HP: fixed = 200.0
|
||||
const BAR_WIDTH: fixed = 64.0
|
||||
|
||||
var hp: fixed = 150.0
|
||||
|
||||
handler Draw phase Render {
|
||||
let width = Math.remap(hp, 0.0, MAX_HP, 0.0, BAR_WIDTH) # 48.0
|
||||
}
|
||||
}
|
||||
```
|
||||
27
docs/language/math/math-round.md
Normal file
27
docs/language/math/math-round.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
id: math-round
|
||||
name: Math.round
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.round
|
||||
sig: Math.round(x) -> int
|
||||
tip: Round a fixed value to the nearest whole int.
|
||||
order: 7
|
||||
ns: Math
|
||||
member: round
|
||||
---
|
||||
|
||||
Takes a <code>fixed</code> value and returns the nearest <code>int</code>, with halves rounding up: <code>Math.round(2.5)</code> is <code>3</code> and <code>Math.round(2.4)</code> is <code>2</code>. Choose it over <code>Math.floor</code> when you want the closest whole value rather than always the lower one — snapping a smoothly-moving sprite to a pixel, or reporting a fractional total as a clean number.
|
||||
|
||||
Parameters:
|
||||
- `x` — the `fixed` value to round to nearest
|
||||
|
||||
```ludic
|
||||
program SnapToPixel {
|
||||
var smooth_x: fixed = 10.5
|
||||
|
||||
handler Draw phase Render {
|
||||
let px = Math.round(smooth_x) # 11
|
||||
}
|
||||
}
|
||||
```
|
||||
28
docs/language/math/math-sign.md
Normal file
28
docs/language/math/math-sign.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
id: math-sign
|
||||
name: Math.sign
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.sign
|
||||
sig: Math.sign(x) -> int
|
||||
tip: The sign of a value as -1, 0, or 1.
|
||||
order: 4
|
||||
ns: Math
|
||||
member: sign
|
||||
---
|
||||
|
||||
Returns <code>-1</code> when <code>x</code> is negative, <code>1</code> when it is positive, and <code>0</code> when it is exactly zero. It works on both <code>int</code> and <code>fixed</code> values, since zero and sign are the same in Q16.16. Multiply a step by <code>Math.sign(dx)</code> to move one unit toward a target, or read the sign of a velocity to decide which way a sprite faces.
|
||||
|
||||
Parameters:
|
||||
- `x` — the value to test
|
||||
|
||||
```ludic
|
||||
program FaceTarget {
|
||||
var x: int = 8
|
||||
var target: int = 20
|
||||
|
||||
handler StepToward phase Update {
|
||||
x = x + Math.sign(target - x) # +1 while target is to the right
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-sin.md
Normal file
22
docs/language/math/math-sin.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-sin
|
||||
name: Math.sin
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.sin
|
||||
sig: Math.sin(radians) -> fixed
|
||||
tip: Sine of an angle in radians.
|
||||
order: 12
|
||||
ns: Math
|
||||
member: sin
|
||||
---
|
||||
|
||||
Returns the sine of <code>radians</code> as a <code>fixed</code> in <code>-1.0</code>..<code>1.0</code>. It is table-driven (a 256-entry Q16.16 sine table with linear interpolation) so it is fast and fully deterministic. Angles wrap, so any value works — no need to reduce into a range first. Pair it with <code>Math.cos</code> to turn an angle into a direction.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let y = center_y + Math.sin(angle) * radius
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-smoothstep.md
Normal file
22
docs/language/math/math-smoothstep.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-smoothstep
|
||||
name: Math.smoothstep
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.smoothstep
|
||||
sig: Math.smoothstep(e0, e1, x) -> fixed
|
||||
tip: A smooth 0..1 ramp between two edges.
|
||||
order: 25
|
||||
ns: Math
|
||||
member: smoothstep
|
||||
---
|
||||
|
||||
Returns <code>0.0</code> when <code>x</code> is at or below <code>e0</code>, <code>1.0</code> at or above <code>e1</code>, and a smooth S-curve (ease-in and ease-out) in between — the classic <code>t*t*(3 - 2t)</code> Hermite ramp. Use it for fades, dissolves, and easing a normalized amount before feeding it to <code>Math.lerp</code>, when a straight linear ramp looks too abrupt.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let a = Math.smoothstep(0.0, 1.0, fade_t)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-snapped.md
Normal file
22
docs/language/math/math-snapped.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-snapped
|
||||
name: Math.snapped
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.snapped
|
||||
sig: Math.snapped(v, step) -> fixed
|
||||
tip: Round a value to the nearest multiple of step.
|
||||
order: 23
|
||||
ns: Math
|
||||
member: snapped
|
||||
---
|
||||
|
||||
Rounds <code>v</code> to the nearest multiple of <code>step</code> (both <code>fixed</code>) — for snapping a position to a grid, quantizing an angle to 8 directions, or rounding a value to a tidy increment. <code>Math.snapped(x, 16.0)</code> snaps <code>x</code> to a 16-unit grid.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let gx = Math.snapped(world_x, 16.0)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-sqrt.md
Normal file
22
docs/language/math/math-sqrt.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-sqrt
|
||||
name: Math.sqrt
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.sqrt
|
||||
sig: Math.sqrt(x) -> fixed
|
||||
tip: The square root of a fixed value.
|
||||
order: 11
|
||||
ns: Math
|
||||
member: sqrt
|
||||
---
|
||||
|
||||
Returns the non-negative square root of <code>x</code>, computed in Q16.16 by a deterministic integer algorithm — so it is bit-identical on every platform, the same guarantee the rest of the runtime gives. Negative inputs return <code>0</code>. Use it for lengths and distances; when you have two legs of a right triangle reach for <code>Math.hypot</code>, and when you only need to compare magnitudes prefer the cheaper squared distance <code>Math.dist2</code>.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let speed = Math.sqrt(vx * vx + vy * vy)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-tan.md
Normal file
22
docs/language/math/math-tan.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-tan
|
||||
name: Math.tan
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.tan
|
||||
sig: Math.tan(radians) -> fixed
|
||||
tip: Tangent of an angle in radians.
|
||||
order: 14
|
||||
ns: Math
|
||||
member: tan
|
||||
---
|
||||
|
||||
Returns the tangent of <code>radians</code>, computed as <code>Math.sin(radians) / Math.cos(radians)</code> in fixed-point. Near odd multiples of <code>pi/2</code> the cosine approaches zero and the result grows without bound, so guard those angles. For most gameplay, <code>Math.sin</code>/<code>Math.cos</code> are what you want; <code>tan</code> is here for slopes and field-of-view math.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let slope = Math.tan(pitch)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
docs/language/math/math-wrap.md
Normal file
22
docs/language/math/math-wrap.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
id: math-wrap
|
||||
name: Math.wrap
|
||||
category: math
|
||||
kind: namespace-method
|
||||
tokens: Math.wrap
|
||||
sig: Math.wrap(v, lo, hi) -> int
|
||||
tip: Wrap a value into the range [lo, hi).
|
||||
order: 21
|
||||
ns: Math
|
||||
member: wrap
|
||||
---
|
||||
|
||||
Wraps <code>v</code> into the half-open range <code>lo</code>..<code>hi</code>, so values that run off one end reappear at the other — the toroidal counterpart to <code>Math.clamp</code>. Use it for wrap-around movement, cycling through a menu, or keeping a scrolling offset in bounds.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
let x = Math.wrap(player_x, 0, world_width)
|
||||
}
|
||||
}
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue