Merge branch 'worktree-agent-ac605f8a376484762' into dev-loop

# Conflicts:
#	NEXT.md
This commit is contained in:
Joseph Ferano 2026-09-13 15:24:28 +07:00
commit 95697d230f
9 changed files with 586 additions and 76 deletions

94
NEXT.md
View File

@ -238,16 +238,54 @@ would cost.
## ~~To discuss: five gaps the raylib examples hit and could not close~~ — **three closed, two live elsewhere**
- **Closed:** an enum-typed `defstruct` field is no longer refused by the layout check (an enum *is* an `i32`, and
the predicate is symmetric now, so the enum may be on either side); the header check reaches `defconst` and
`defenum` through name-mapping directives in `vendor/raylib/bindings`, so a wrong flag bit is no longer silent;
and the `Ptr`-indexing gap has its own queue entry at the top of this file, because it now blocks three
examples rather than being a note.
- **Still open, and both are here rather than in a queue entry because neither has a customer pressing:**
**raymath is `static inline`**, so `Clamp`, `Vector2Add` and `Remap` have no symbol to `declare-c` at all — a lane
measured the alternative and found writing the arithmetic in Flan cost nothing, which is what makes this a
non-problem rather than a gap; and **function-pointer parameters** are refused (`SetTraceLogCallback`, the audio
stream processors), which is the callback direction of the FFI and nothing has needed it yet.
Found by the lane that ported `core-2d-camera`, `core-scissor-test`, `core-window-flags`,
`core-world-screen` and `core-window-should-close`. None blocked those five. All five blocked
something else, and each is a language or checker question rather than a missing binding, which is
why they are here and not in a binding list.
1. **A `(Ptr T)` returned from C cannot be indexed.** `indexed` in `lib/check.ml` accepts `Array`
and `Slice` only, so a C function answering `int*` is readable at element 0 through `deref` and
nowhere else. `LoadRandomSequence` is the case that hit it and `core_random_sequence` is
unportable until it moves. The question is what the answer should be: a length has to come from
somewhere before a pointer can become a slice, and C does not supply one. Possibly a
`(slice-from-ptr p n)` where the caller states the length and owns being right about it.
**This has its own queue entry at the top of this file now** — it blocks three more examples than this
section knew about, and both candidate shapes are written out there.
2. ~~**An enum-typed `defstruct` field is refused by the layout check.**~~ **Closed.** The layout
check now accepts an enum where the header says `int`, symmetrically, and still refuses
anything that is not four bytes. `Camera3D.projection` is a `CameraProjection` again and the
`rl/camera-projection` helper is gone; `.projection :perspective` resolves at the construction
site, so the keyword half of the problem went away with it. See BUILT.md.
3. ~~**The header check does not reach `defconst` or `defenum`.**~~ **Closed.** It reaches both.
`bindings` gained `enum`, `const` and `constant` lines that say what a Flan constant is called in
C; every mapped name is compared by value, and a name the mapping cannot find, a rule that
reaches nothing, and a `defenum` with no line at all are each reported rather than skipped. All
eight raylib enums and all 16 `ConfigFlags` bits check out against 5.5. See BUILT.md.
4. ~~**raymath is `static inline`, so there is no symbol to bind.**~~ **Closed for the vector half.**
`Clamp`, `Vector2Add`, `Remap` and the rest exist only in the header and `declare-c` has nothing
to name, so the arithmetic is written in Flan: `vendor/raylib/vector.flan`, a package file with
no `declare-c` in it at all — which is why it is a file of its own rather than more of
`raylib.flan`, the file the header check reads hand-written signatures out of. Vector2 and
Vector3 add/sub/mul/scale/negate/dot/length/distance/normalize/lerp, `v2-angle`, `v2-rotate`,
`v3-cross`, and `remap`, `inverse-lerp`, `wrap-f32` on scalars. raymath's semantics exactly,
including the zero-length guard in `normalize`. **`clamp` and `lerp` are deliberately absent**:
both are already in the prelude, and a second `lerp` would not even be the same function —
the prelude writes `(1-t)a + tb`, raymath writes `a + t*(b - a)`, and shipping both under names
one letter apart is a footgun. The C-shim alternative was rejected on the measurement
`examples/shapes-following-eyes.flan` already took: it would buy identical arithmetic for a
compilation unit in the build and a second place raylib's semantics are written down. rlgl's
matrix stack is still unbound for a different reason, so `core_2d_camera_mouse_zoom` is still
skipped.
5. **Four families are still refused by the importer for want of a `defstruct`.** `FilePathList`
(a `char**`, blocks `core_drop_files`), `Model`/`Mesh`/`Ray`/`BoundingBox` (the model and
3D-collision families), and **function-pointer parameters** (`SetTraceLogCallback`, which blocks
`core_custom_logging`, and the audio stream processors). The first three are ordinary widening —
write the `defstruct` and they import. The function-pointer one is not, and is the interesting
one: it is the callback direction of the FFI, which nothing has needed yet.
## Queued, 2026-09-13 (second session) — everything four lanes left behind
@ -382,12 +420,34 @@ Interactively the controls are now `r` and left-mouse only. `test_web.ml`'s asse
the wasm module went with the embed: `web-files.flan` is web-built and *run* under node and asserts the embedded
bytes print, which is the same property checked harder.
## Queued: an idiomatic layer over the generated bindings
## ~~Queued: an idiomatic layer over the generated bindings~~ — **landed**
Thin Flan-shaped wrappers **over** the generated bindings, not instead of them. The generated set stays honest to C —
that is what makes it checkable against the header — and the layer is where a Flan-shaped API lives. Two of these
already exist by hand in `vendor/raylib/raylib.flan` and are the shape to copy: `collision-point-poly?` takes a slice
and `collision-lines` answers with an `Option`, each wrapping a `-raw` binding of the same name.
Thin Flan-shaped wrappers **over** the generated bindings, not instead of them. Built as three kinds, listed in the
header of `vendor/raylib/raylib.flan`:
- **A slice where C takes a pointer and a count.** The eleven vector-array drawing calls — `draw-line-strip`, the two
triangle batches, the five splines, the two `image-draw-triangle-*`, `draw-triangle-strip-3d` — under one section.
All eleven and not the three anybody calls: a subset puts the hole where the next caller looks. Each also guards the
empty slice, which is the part a hand-written call site gets wrong rather than merely writes out — raylib takes a
count of 0 happily, but `(addr (at pts 0))` is out of bounds before raylib is reached.
- **An `Option` where C signals with a sentinel.** `get-key-pressed` and `get-char-pressed`, raylib's two input
queues, both of which say "empty" with 0. What it buys is in `examples/text-input-box.flan`: the C shape reads the
queue in two places, once to prime the loop and once at the bottom of the body, and the Option shape reads it in
one.
- **An enum where the header says `int`.** `key-up?`, `key-pressed-repeat?`, `mouse-button-up?` — holes in families
whose other halves already took a `Key` or a `MouseButton`, so `(rl/key-down? :space)` compiled and
`(rl/key-up? :space)` did not. **These are not wrappers.** A C enum parameter has an int's ABI, so the hand-written
`declare-c` with the Flan type on it is the whole fix and a `defn` around it would be a rename.
The mechanism for the first two is the `name` directive in `vendor/raylib/bindings`: the generated declaration keeps
the symbol and gives up the name, so nothing about the C signature is hand-written and the generated half keeps its
agreement-by-construction with the header. The third is an `exclude` plus a hand-written line, exactly as `SetExitKey`
and `SetMouseCursor` already were.
**Not built: `with-drawing` and `with-mode-2d`.** An unbalanced begin/end is a real bug and a macro removes it, but a
macro cannot live in a package — the expander collects `defmacro`s from the prelude and from the file being compiled,
and one in an imported package is refused by name. `test/programs/pkg-macro.flan` is that refusal and its whole content
is the case. They have to be written in the program that uses them, or wait for macros to be importable.
## ~~Queued: a restart is not a transaction, and the docs must say so~~ — **landed**
@ -661,7 +721,9 @@ second.
One smaller thing found and worth not re-deriving: an enum parameter imports as `i32`, because the header says
`KeyboardKey` and nothing tells the importer the package calls that `Key`. The ABI is identical, the face is worse,
and it is why `(rl/key-down? :space)` keeps its hand-written line.
and it is why `(rl/key-down? :space)` keeps its hand-written line. The idiomatic-layer lane closed the three holes
this left — `key-up?`, `key-pressed-repeat?` and `mouse-button-up?` were generated and therefore took an `i32`, so the
sibling of a call that worked did not — by excluding them and hand-writing the enum type, which is all it takes.
### Landed 2026-09-12 — six tracks, one session

View File

@ -54,16 +54,17 @@
;; The same centre/size to min/max conversion as in
;; examples/models-box-collisions.flan. Written out here rather than shared
;; because an example is a single file the reader can follow end to end, and
;; a two-file port for eight expressions would cost more than it saves.
;; because an example is a single file the reader can follow end to end.
;;
;; It used to be eight expressions of field arithmetic; it is the half-extent
;; subtracted and added, which is what the C means, now that the package
;; carries vector arithmetic — see vendor/raylib/vector.flan. That file
;; exists because raymath is `static inline` and has no symbol to bind, so
;; v3-sub and v3-add are Flan and not C.
(defn box-around [centre rl/Vector3 size rl/Vector3] rl/BoundingBox
(rl/BoundingBox
{.min (rl/Vector3 {.x (- (.x centre) (/ (.x size) 2.0))
.y (- (.y centre) (/ (.y size) 2.0))
.z (- (.z centre) (/ (.z size) 2.0))})
.max (rl/Vector3 {.x (+ (.x centre) (/ (.x size) 2.0))
.y (+ (.y centre) (/ (.y size) 2.0))
.z (+ (.z centre) (/ (.z size) 2.0))})}))
(let [half (rl/v3-scale size 0.5)]
(rl/BoundingBox {.min (rl/v3-sub centre half)
.max (rl/v3-add centre half)})))
(defn main [] ()
(rl/init-window screen-width screen-height

View File

@ -54,14 +54,15 @@
;; min-corner/max-corner, not centre/size, and every collision call in the
;; family takes the corner form — so this is the conversion the C writes out
;; longhand at each of its two call sites.
;;
;; It used to be eight expressions of field arithmetic here too; it is the
;; half-extent subtracted and added now that the package carries vector
;; arithmetic — see vendor/raylib/vector.flan, which is Flan and not C
;; because raymath is `static inline` and has no symbol to bind.
(defn box-around [centre rl/Vector3 size rl/Vector3] rl/BoundingBox
(rl/BoundingBox
{.min (rl/Vector3 {.x (- (.x centre) (/ (.x size) 2.0))
.y (- (.y centre) (/ (.y size) 2.0))
.z (- (.z centre) (/ (.z size) 2.0))})
.max (rl/Vector3 {.x (+ (.x centre) (/ (.x size) 2.0))
.y (+ (.y centre) (/ (.y size) 2.0))
.z (+ (.z centre) (/ (.z size) 2.0))})}))
(let [half (rl/v3-scale size 0.5)]
(rl/BoundingBox {.min (rl/v3-sub centre half)
.max (rl/v3-add centre half)})))
(defn main [] ()
(rl/init-window screen-width screen-height

View File

@ -17,6 +17,16 @@
;;;; has. It reads fine. A Vector2Add would have saved two lines in the whole
;;;; file.
;;;;
;;;; That measurement is what decided the answer, and the answer landed:
;;;; vendor/raylib/vector.flan is raymath written in Flan, since a C shim
;;;; re-exporting the inlines would have bought identical arithmetic at the
;;;; price of a compilation unit and a second place raylib's semantics live.
;;;; The measurement is left standing rather than rewritten away — one
;;;; subtraction below is `rl/v2-sub` and the rest of the file is unchanged,
;;;; including the atan2/cos/sin path, which is deliberate: it needs no guard
;;;; for a zero-length vector where a normalise would. One line saved, in a
;;;; file that is nothing but vector maths, is the honest size of the gap.
;;;;
;;;; The raylib call it does exercise is collision-point-circle?, which no
;;;; ported example had called, on a frame path, with the point coming
;;;; straight out of get-mouse-position — one struct out of raylib and back
@ -56,9 +66,8 @@
;; because that is what the C does and because it needs no guard for a
;; zero-length vector — atan2(0,0) is 0 and the pupil sits at the right
;; of the eye, which is unreachable anyway since (0,0) is inside.
(let [dx (- (.x mouse) (.x centre))
dy (- (.y mouse) (.y centre))
angle (atan2-f32 dy dx)]
(let [d (rl/v2-sub mouse centre)
angle (atan2-f32 (.y d) (.x d))]
(rl/Vector2 {.x (+ (.x centre) (* limit (cos-f32 angle)))
.y (+ (.y centre) (* limit (sin-f32 angle)))})))))

View File

@ -7,11 +7,19 @@
;;;; state — key-down?, key-pressed?, the mouse position — and this is the
;;;; only example that drains a *queue*: raylib buffers the characters the
;;;; platform produced since the last frame, already through the keyboard
;;;; layout and the dead keys, and hands them back one at a time until it
;;;; answers 0. The loop that empties it is the point of the example, and a
;;;; layout and the dead keys, and hands them back one at a time until it is
;;;; empty. The loop that empties it is the point of the example, and a
;;;; program that read the queue once per frame instead would silently drop
;;;; the second character of a fast pair. It is a plain i32 out of the
;;;; generated bindings; nothing needed adding for it.
;;;; the second character of a fast pair.
;;;;
;;;; raylib says "empty" by answering 0, and rl/get-char-pressed is now a
;;;; Flan wrapper that says it with None instead — see raylib.flan, "Draining
;;;; raylib's two input queues". What that buys is visible below: the C
;;;; shape, which this file had, reads the queue in *two* places, once to
;;;; prime the loop and once at the bottom of the body, and a `> 0` in
;;;; between that a reader has to know raylib's convention to trust. The
;;;; Option shape reads it in one place, and the case where there is no
;;;; character is a branch the checker knows about rather than a comparison.
;;;;
;;;; The second is set-mouse-cursor, which needed a new defenum. raylib's
;;;; header says `int cursor` and means one of eleven MOUSE_CURSOR_ values, so
@ -75,19 +83,22 @@
(do
(rl/set-mouse-cursor :ibeam)
;; Drain the character queue. raylib answers 0 when it is empty, and
;; more than one character can arrive in a single frame — holding a
;; key with the platform's repeat on is the ordinary way that happens.
(let [key (rl/get-char-pressed)]
(while (> key 0)
;; 32..125 is the printable ASCII range the C accepts. Anything
;; outside it — an accented letter, a control character — is
;; dropped rather than stored, because the buffer is bytes and a
;; codepoint above 127 would need more than one of them.
(when (and (>= key 32) (<= key 125) (< letter-count max-input-chars))
(set (at name letter-count) (u8 key))
(set letter-count (+ letter-count 1)))
(set key (rl/get-char-pressed))))
;; Drain the character queue: more than one character can arrive in
;; a single frame — holding a key with the platform's repeat on is
;; the ordinary way that happens — and None is the end of it.
(let [draining true]
(while draining
(match (rl/get-char-pressed)
None (set draining false)
;; 32..125 is the printable ASCII range the C accepts. Anything
;; outside it — an accented letter, a control character — is
;; dropped rather than stored, because the buffer is bytes and a
;; codepoint above 127 would need more than one of them.
(Some key)
(when (and (>= key 32) (<= key 125)
(< letter-count max-input-chars))
(set (at name letter-count) (u8 key))
(set letter-count (+ letter-count 1))))))
(when (rl/key-pressed? :backspace)
(set letter-count (- letter-count 1))

View File

@ -57,9 +57,6 @@ name IsWindowFocused window-focused?
name IsWindowResized window-resized?
name IsWindowState window-state?
name IsCursorOnScreen cursor-on-screen?
name IsKeyUp key-up?
name IsKeyPressedRepeat key-pressed-repeat?
name IsMouseButtonUp mouse-button-up?
name IsFileDropped file-dropped?
name IsFileExtension file-extension?
name IsFileNameValid file-name-valid?
@ -139,6 +136,53 @@ exclude DrawSphereWires
exclude DrawRay
exclude GetScreenToWorldRay
# ── The idiomatic layer, which is what these last two blocks are for ──
#
# Three kinds of C signature get a Flan face in raylib.flan rather than the
# generated one, and each kind is a directive here so that the generated file
# does not also define the name. See the "An idiomatic layer" section of
# raylib.flan for what each wrapper buys at the call site.
#
# 1. An `int` parameter the package already has a defenum for. These are
# excluded and hand-written with the enum type, exactly as SetExitKey and
# SetMouseCursor already are, and for the same reason: a keyword resolves
# against the members at compile time and a typo is an error there. What
# makes these three different from those is that they are *holes in a
# family that already exists* — key-down? takes a Key and key-up? took an
# i32, so `(rl/key-up? :space)` did not compile while `(rl/key-down?
# :space)` did. A wrapper would be a pure rename; the fix is the
# declaration.
exclude IsKeyUp
exclude IsKeyPressedRepeat
exclude IsMouseButtonUp
# 2. A sentinel return, wrapped by a Flan defn that answers an Option. The
# generated declaration is still what calls C and is still checked against
# the header — only its *name* moves aside, which is what `name` is for.
# Nothing about the signature is wrong, so there is no reason to hand-write
# it and lose the generated half's by-construction agreement.
name GetCharPressed get-char-pressed-raw
name GetKeyPressed get-key-pressed-raw
# 3. A pointer-and-count pair where Flan has a slice. Same treatment and the
# same reason: the C signature is right, the Flan face is a slice, so the
# generated line keeps the symbol and the wrapper takes the name. This is
# every raylib entry point that takes an array of vectors as pointer plus
# count, and it is the whole family on purpose — a subset would put the
# hole exactly where the next caller looks, which is the argument
# raylib.flan makes about ConfigFlags.
name DrawLineStrip draw-line-strip-raw
name DrawTriangleFan draw-triangle-fan-raw
name DrawTriangleStrip draw-triangle-strip-raw
name DrawTriangleStrip3D draw-triangle-strip-3d-raw
name DrawSplineLinear draw-spline-linear-raw
name DrawSplineBasis draw-spline-basis-raw
name DrawSplineCatmullRom draw-spline-catmull-rom-raw
name DrawSplineBezierQuadratic draw-spline-bezier-quadratic-raw
name DrawSplineBezierCubic draw-spline-bezier-cubic-raw
name ImageDrawTriangleFan image-draw-triangle-fan-raw
name ImageDrawTriangleStrip image-draw-triangle-strip-raw
# ── What the package's constants are called in C ────────────────────
#
# enum <FlanEnum> <C_PREFIX> every member of that defenum

View File

@ -1,4 +1,4 @@
;;;; Generated from raylib.h by `flan generate-c`. Do not edit this file.
;;;; Generated from raylib-5.5.h by `flan generate-c`. Do not edit this file.
;;;;
;;;; Every line here was read out of the C header named by `headers`, and
;;;; the next regeneration overwrites the file — so a correction made here
@ -10,7 +10,7 @@
;;;;
;;;; Regenerating compares the package against the header first and
;;;; refuses to write when they disagree, so this file and the
;;;; hand-written declarations beside it agreed with raylib.h when it was made.
;;;; hand-written declarations beside it agreed with raylib-5.5.h when it was made.
(declare-c window-fullscreen? [] bool "IsWindowFullscreen")
(declare-c window-hidden? [] bool "IsWindowHidden")
@ -93,13 +93,10 @@
(declare-c set-automation-event-base-frame [frame i32] "SetAutomationEventBaseFrame")
(declare-c start-automation-event-recording [] "StartAutomationEventRecording")
(declare-c stop-automation-event-recording [] "StopAutomationEventRecording")
(declare-c key-pressed-repeat? [key i32] bool "IsKeyPressedRepeat")
(declare-c key-up? [key i32] bool "IsKeyUp")
(declare-c get-key-pressed [] i32 "GetKeyPressed")
(declare-c get-char-pressed [] i32 "GetCharPressed")
(declare-c get-key-pressed-raw [] i32 "GetKeyPressed")
(declare-c get-char-pressed-raw [] i32 "GetCharPressed")
(declare-c set-gamepad-mappings [mappings string] i32 "SetGamepadMappings")
(declare-c set-gamepad-vibration [gamepad i32 left-motor f32 right-motor f32 duration f32] "SetGamepadVibration")
(declare-c mouse-button-up? [button i32] bool "IsMouseButtonUp")
(declare-c get-mouse-x [] i32 "GetMouseX")
(declare-c get-mouse-y [] i32 "GetMouseY")
(declare-c get-mouse-delta [] Vector2 "GetMouseDelta")
@ -108,7 +105,7 @@
(declare-c set-mouse-scale [scale-x f32 scale-y f32] "SetMouseScale")
(declare-c get-mouse-wheel-move-v [] Vector2 "GetMouseWheelMoveV")
(declare-c update-camera-pro [camera (Ptr Camera3D) movement Vector3 rotation Vector3 zoom f32] "UpdateCameraPro")
(declare-c draw-line-strip [points (Ptr Vector2) point-count i32 color Color] "DrawLineStrip")
(declare-c draw-line-strip-raw [points (Ptr Vector2) point-count i32 color Color] "DrawLineStrip")
(declare-c draw-line-bezier [start-pos Vector2 end-pos Vector2 thick f32 color Color] "DrawLineBezier")
(declare-c draw-circle-sector [center Vector2 radius f32 start-angle f32 end-angle f32 segments i32 color Color] "DrawCircleSector")
(declare-c draw-circle-sector-lines [center Vector2 radius f32 start-angle f32 end-angle f32 segments i32 color Color] "DrawCircleSectorLines")
@ -117,16 +114,16 @@
(declare-c draw-rectangle-gradient-v [pos-x i32 pos-y i32 width i32 height i32 top Color bottom Color] "DrawRectangleGradientV")
(declare-c draw-rectangle-gradient-h [pos-x i32 pos-y i32 width i32 height i32 left Color right Color] "DrawRectangleGradientH")
(declare-c draw-rectangle-gradient-ex [rec Rectangle top-left Color bottom-left Color top-right Color bottom-right Color] "DrawRectangleGradientEx")
(declare-c draw-triangle-fan [points (Ptr Vector2) point-count i32 color Color] "DrawTriangleFan")
(declare-c draw-triangle-strip [points (Ptr Vector2) point-count i32 color Color] "DrawTriangleStrip")
(declare-c draw-triangle-fan-raw [points (Ptr Vector2) point-count i32 color Color] "DrawTriangleFan")
(declare-c draw-triangle-strip-raw [points (Ptr Vector2) point-count i32 color Color] "DrawTriangleStrip")
(declare-c draw-poly [center Vector2 sides i32 radius f32 rotation f32 color Color] "DrawPoly")
(declare-c draw-poly-lines [center Vector2 sides i32 radius f32 rotation f32 color Color] "DrawPolyLines")
(declare-c draw-poly-lines-ex [center Vector2 sides i32 radius f32 rotation f32 line-thick f32 color Color] "DrawPolyLinesEx")
(declare-c draw-spline-linear [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineLinear")
(declare-c draw-spline-basis [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineBasis")
(declare-c draw-spline-catmull-rom [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineCatmullRom")
(declare-c draw-spline-bezier-quadratic [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineBezierQuadratic")
(declare-c draw-spline-bezier-cubic [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineBezierCubic")
(declare-c draw-spline-linear-raw [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineLinear")
(declare-c draw-spline-basis-raw [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineBasis")
(declare-c draw-spline-catmull-rom-raw [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineCatmullRom")
(declare-c draw-spline-bezier-quadratic-raw [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineBezierQuadratic")
(declare-c draw-spline-bezier-cubic-raw [points (Ptr Vector2) point-count i32 thick f32 color Color] "DrawSplineBezierCubic")
(declare-c draw-spline-segment-linear [p-1 Vector2 p-2 Vector2 thick f32 color Color] "DrawSplineSegmentLinear")
(declare-c draw-spline-segment-basis [p-1 Vector2 p-2 Vector2 p-3 Vector2 p-4 Vector2 thick f32 color Color] "DrawSplineSegmentBasis")
(declare-c draw-spline-segment-catmull-rom [p-1 Vector2 p-2 Vector2 p-3 Vector2 p-4 Vector2 thick f32 color Color] "DrawSplineSegmentCatmullRom")
@ -197,8 +194,8 @@
(declare-c image-draw-triangle [dst (Ptr Image) v-1 Vector2 v-2 Vector2 v-3 Vector2 color Color] "ImageDrawTriangle")
(declare-c image-draw-triangle-ex [dst (Ptr Image) v-1 Vector2 v-2 Vector2 v-3 Vector2 c-1 Color c-2 Color c-3 Color] "ImageDrawTriangleEx")
(declare-c image-draw-triangle-lines [dst (Ptr Image) v-1 Vector2 v-2 Vector2 v-3 Vector2 color Color] "ImageDrawTriangleLines")
(declare-c image-draw-triangle-fan [dst (Ptr Image) points (Ptr Vector2) point-count i32 color Color] "ImageDrawTriangleFan")
(declare-c image-draw-triangle-strip [dst (Ptr Image) points (Ptr Vector2) point-count i32 color Color] "ImageDrawTriangleStrip")
(declare-c image-draw-triangle-fan-raw [dst (Ptr Image) points (Ptr Vector2) point-count i32 color Color] "ImageDrawTriangleFan")
(declare-c image-draw-triangle-strip-raw [dst (Ptr Image) points (Ptr Vector2) point-count i32 color Color] "ImageDrawTriangleStrip")
(declare-c image-draw [dst (Ptr Image) src Image src-rec Rectangle dst-rec Rectangle tint Color] "ImageDraw")
(declare-c image-draw-text [dst (Ptr Image) text string pos-x i32 pos-y i32 font-size i32 color Color] "ImageDrawText")
(declare-c image-draw-text-ex [dst (Ptr Image) font Font text string position Vector2 font-size f32 spacing f32 tint Color] "ImageDrawTextEx")
@ -245,7 +242,7 @@
(declare-c draw-point-3d [position Vector3 color Color] "DrawPoint3D")
(declare-c draw-circle-3d [center Vector3 radius f32 rotation-axis Vector3 rotation-angle f32 color Color] "DrawCircle3D")
(declare-c draw-triangle-3d [v-1 Vector3 v-2 Vector3 v-3 Vector3 color Color] "DrawTriangle3D")
(declare-c draw-triangle-strip-3d [points (Ptr Vector3) point-count i32 color Color] "DrawTriangleStrip3D")
(declare-c draw-triangle-strip-3d-raw [points (Ptr Vector3) point-count i32 color Color] "DrawTriangleStrip3D")
(declare-c draw-cube-wires-v [position Vector3 size Vector3 color Color] "DrawCubeWiresV")
(declare-c draw-sphere-ex [center-pos Vector3 radius f32 rings i32 slices i32 color Color] "DrawSphereEx")
(declare-c draw-cylinder [position Vector3 radius-top f32 radius-bottom f32 height f32 slices i32 color Color] "DrawCylinder")

View File

@ -27,9 +27,29 @@
;;;; f64 where raylib says float now emits `double` in the generated
;;;; prototype, and raylib reads garbage.
;;;;
;;;; Two bindings keep a hand-written Flan wrapper, both because their Flan
;;;; face is deliberately not raylib's: collision-point-poly? takes a slice,
;;;; and collision-lines answers with an Option. Both wrappers are Flan.
;;;; Some bindings keep a hand-written Flan wrapper, because their Flan face
;;;; is deliberately not raylib's. Three shapes of that, and every wrapper in
;;;; this file is one of them:
;;;;
;;;; - a slice where C takes a pointer and a count — collision-point-poly?,
;;;; load-image-from-memory, load-font-ex, and the eleven vector-array
;;;; drawing calls under "A slice where raylib wants a pointer and a
;;;; count";
;;;; - an Option where C signals failure by a bool out-parameter or a
;;;; sentinel — collision-lines, get-key-pressed, get-char-pressed;
;;;; - an enum where the header says `int`. These are NOT wrappers: a C
;;;; enum parameter has an int's ABI, so the hand-written declare-c with
;;;; the Flan type on it is the whole fix, and set-exit-key,
;;;; set-mouse-cursor, key-up? and mouse-button-up? are all that.
;;;;
;;;; What is NOT here, and was asked for: with-drawing and with-mode-2d over
;;;; raylib's begin/end pairs. An unbalanced pair is a real bug and a macro
;;;; removes it, but a macro cannot live in a package — the expander collects
;;;; defmacros from the prelude and from the file being compiled, and a
;;;; defmacro in an imported package is refused by name
;;;; (test/programs/pkg-macro.flan, an acceptance case whose whole content is
;;;; the refusal). So these have to be written in the program that uses them,
;;;; or wait for macros to be importable, and neither is this file's to do.
;; Layouts are C's — no object headers anywhere — so these are exactly
;; raylib's structs and nothing marshals.
@ -133,6 +153,18 @@
(declare-c key-down? [key Key] bool "IsKeyDown")
(declare-c key-released? [key Key] bool "IsKeyReleased")
;; The other two halves of that family, hand-written for exactly the reason
;; above and added late: they were generated, so they took an i32, so
;; `(rl/key-up? :space)` did not compile while `(rl/key-down? :space)` did.
;; That is a hole in a family rather than a missing convenience — a caller
;; who has used key-down? has no reason to expect the sibling to be spelled
;; differently, and what they get instead of a keyword is a number nobody
;; checks. Nothing wraps these: the ABI of a C enum parameter is the ABI of
;; an int, so the declaration IS the fix and a defn around it would only be
;; a rename.
(declare-c key-up? [key Key] bool "IsKeyUp")
(declare-c key-pressed-repeat? [key Key] bool "IsKeyPressedRepeat")
(declare-c mouse-button-pressed?
[button MouseButton] bool
"IsMouseButtonPressed")
@ -140,6 +172,37 @@
(declare-c mouse-button-released?
[button MouseButton] bool
"IsMouseButtonReleased")
(declare-c mouse-button-up? [button MouseButton] bool "IsMouseButtonUp")
;; ── Draining raylib's two input queues ──────────────────────────────
;;
;; Both of these answer "nothing left" with 0, and 0 is also a value the
;; caller could otherwise have to think about — KEY_NULL for one, the NUL
;; byte for the other. An Option says which of the two it is in the type, so
;; the loop that drains the queue cannot read the sentinel as a key or as a
;; character: `while (> key 0)` is a comparison a reader has to know the
;; convention to trust, and `(while-some ...)` — or the `if-let` shape the
;; examples use — is one a reader can check.
;;
;; The generated declarations are still what call C; only their names moved
;; aside, to -raw, via the `name` lines in `bindings`. Nothing about the C
;; signature was wrong, so hand-writing it would have taken the generated
;; half's agreement-by-construction with the header and given nothing back.
;;
;; get-key-pressed answers an i32 and not a Key. A Key is a *closed* set the
;; package names a subset of, and this queue reports every key on the
;; keyboard including the ones no member covers, so the enum would be a
;; promise the value does not keep. Comparing the answer against `:space`
;; would be the reason to want it, and that is what key-pressed? is for.
(defn get-key-pressed [] (Option i32)
(let [k (get-key-pressed-raw)]
(if (= k 0) None (Some k))))
;; Unicode codepoint, not a byte: raylib decodes the platform's input, so a
;; value above 127 is a real codepoint and not the first byte of one.
(defn get-char-pressed [] (Option i32)
(let [c (get-char-pressed-raw)]
(if (= c 0) None (Some c))))
(declare-c get-mouse-position [] Vector2 "GetMousePosition")
@ -793,6 +856,95 @@
(declare-c draw-rectangle-rounded-lines-ex [rec Rectangle roundness f32
segments i32 thick f32 color Color] "DrawRectangleRoundedLinesEx")
;; ── A slice where raylib wants a pointer and a count ─────────────────
;;
;; Eleven entry points take an array of vectors as a pointer plus an `int`
;; count. A Flan slice already carries both, so every call site that does not
;; go through a wrapper has to take the slice apart itself — `(addr (at pts
;; 0))` and `(len pts)`, twice, in the right order — and the compiler cannot
;; check that the two halves came from the same slice. The wrapper is where
;; that idiom lives, which is the rule collision-point-poly? set.
;;
;; It also guards the empty case, which is the part a hand-written call site
;; gets wrong rather than merely writes out. raylib takes a count of 0 and
;; draws nothing, but `(at pts 0)` on an empty slice is out of bounds before
;; raylib is ever reached: the safe call is "do not call at all", and it is
;; written once here instead of at every use.
;;
;; All eleven and not the three anybody has called. A subset would have its
;; hole exactly where the next caller looks, which is the argument this file
;; already makes about ConfigFlags, and the eleven are one family — there is
;; no line to draw between DrawSplineLinear and DrawSplineBasis that a reader
;; would predict.
;;
;; `bindings` makes the opposite argument a few lines above its own list —
;; that hand-writing the variants of a family "would widen the half that has
;; to be maintained by hand for nothing the examples ask for" — and it is
;; right there and does not reach here. That paragraph is about hand-written
;; `declare-c` lines, which are exactly the half a header change can falsify.
;; None of these eleven is one: each is a `name` directive, so the generated
;; declaration keeps the C symbol and its checked signature and gives up only
;; its Flan name. The hand-maintained half does not widen at all — what is
;; written below is Flan calling Flan, and it cannot disagree with raylib. They sit together here rather than each in its own section
;; for the same reason: the justification above is one argument about a shape
;; that cuts across Shapes, Images and 3D, and splitting the family would
;; mean writing it three times or leaving two thirds of it unexplained.
;;
;; Each -raw below is a generated declaration whose name moved aside; see the
;; `name` lines at the foot of `bindings`.
(defn draw-line-strip [points [Vector2] color Color] ()
(when (> (len points) 0)
(draw-line-strip-raw (addr (at points 0)) (len points) color)))
(defn draw-triangle-fan [points [Vector2] color Color] ()
(when (> (len points) 0)
(draw-triangle-fan-raw (addr (at points 0)) (len points) color)))
(defn draw-triangle-strip [points [Vector2] color Color] ()
(when (> (len points) 0)
(draw-triangle-strip-raw (addr (at points 0)) (len points) color)))
(defn draw-triangle-strip-3d [points [Vector3] color Color] ()
(when (> (len points) 0)
(draw-triangle-strip-3d-raw (addr (at points 0)) (len points) color)))
;; The five spline drawers. raylib reads the same point array five different
;; ways; the only difference between these wrappers is which one it calls.
(defn draw-spline-linear [points [Vector2] thick f32 color Color] ()
(when (> (len points) 0)
(draw-spline-linear-raw (addr (at points 0)) (len points) thick color)))
(defn draw-spline-basis [points [Vector2] thick f32 color Color] ()
(when (> (len points) 0)
(draw-spline-basis-raw (addr (at points 0)) (len points) thick color)))
(defn draw-spline-catmull-rom [points [Vector2] thick f32 color Color] ()
(when (> (len points) 0)
(draw-spline-catmull-rom-raw (addr (at points 0)) (len points) thick color)))
(defn draw-spline-bezier-quadratic [points [Vector2] thick f32 color Color] ()
(when (> (len points) 0)
(draw-spline-bezier-quadratic-raw
(addr (at points 0)) (len points) thick color)))
(defn draw-spline-bezier-cubic [points [Vector2] thick f32 color Color] ()
(when (> (len points) 0)
(draw-spline-bezier-cubic-raw
(addr (at points 0)) (len points) thick color)))
;; The same two into an Image rather than the frame. `dst` stays a pointer:
;; it is the thing being written, not an array, and raylib's convention for
;; an in-place Image is the whole Image* family in this file.
(defn image-draw-triangle-fan [dst (Ptr Image) points [Vector2] color Color] ()
(when (> (len points) 0)
(image-draw-triangle-fan-raw dst (addr (at points 0)) (len points) color)))
(defn image-draw-triangle-strip
[dst (Ptr Image) points [Vector2] color Color] ()
(when (> (len points) 0)
(image-draw-triangle-strip-raw dst (addr (at points 0)) (len points) color)))
;; ── Text ────────────────────────────────────────────────────────────
;;
;; Both of these use raylib's built-in font, and both therefore need

233
vendor/raylib/vector.flan vendored Normal file
View File

@ -0,0 +1,233 @@
;;;; Vector arithmetic over raylib's Vector2 and Vector3, in Flan.
;;;;
;;;; This is raymath, and raymath is the one part of raylib that cannot be
;;;; bound at all. raymath.h defines every one of its functions `static
;;;; inline` (RMAPI expands to it), so Vector2Add and Clamp and Remap have no
;;;; symbol in libraylib for `declare-c` to name — not a signature the
;;;; importer gets wrong, not a struct the package has not described, but
;;;; nothing to link against. NEXT.md item 4 records it and names the two
;;;; ways out: write the arithmetic in Flan, or compile a small C file that
;;;; re-exports the inlines as real symbols.
;;;;
;;;; It is written in Flan, and the measurement that decided it was already
;;;; taken: examples/shapes-following-eyes.flan is an example whose every
;;;; line is vector maths, ported without a vector library, and its own
;;;; header reports that this cost nothing — the C does not use raymath there
;;;; either. A C shim would buy identical arithmetic at the price of a
;;;; compilation unit in the build, a second place raylib's semantics are
;;;; written down, and a third target's worth of it for the web build.
;;;;
;;;; Every function here is raymath's, semantics included, and the ones where
;;;; that is not obvious say so. The one worth knowing without reading: at
;;;; zero length, v2-normalize and v3-normalize answer the zero vector rather
;;;; than dividing and producing NaNs. raymath makes that choice and a caller
;;;; who has raymath in mind would be surprised by the other one.
;;;;
;;;; ── Why this is a file of its own ───────────────────────────────────
;;;;
;;;; The split is on `declare-c`, not on "idiomatic". raylib.flan is the
;;;; package's statement about C: every line in it is a declaration or a thin
;;;; wrapper over one, it is the file the header check reads hand-written
;;;; signatures out of, and a wrong line in it stops the build. There is not
;;;; one `declare-c` below and there never will be, because there is nothing
;;;; to declare — so nothing here can be checked against a header, and
;;;; nothing here can be made wrong by raylib changing. A reader who wants to
;;;; know what the package claims about C should not have to walk past four
;;;; hundred lines of float arithmetic to find out, and 1300 lines of
;;;; raylib.flan is already the argument against adding to it.
;;;;
;;;; A package is a directory, so this is simply another .flan beside the
;;;; others and is qualified `rl/` like the rest of it.
;;;;
;;;; ── Names ───────────────────────────────────────────────────────────
;;;;
;;;; `v2-` and `v3-`, not `vector2-`. These appear nested inside each other —
;;;; `(rl/v2-add p (rl/v2-scale d t))` is the ordinary shape — and the longer
;;;; spelling puts more characters between the reader and the arithmetic than
;;;; it puts meaning. The prefix still says the type, which is the part a
;;;; language without generics needs it to say.
;;;;
;;;; ── What is NOT here, on purpose ────────────────────────────────────
;;;;
;;;; `clamp` and `lerp`. Both are already in the prelude — clamp as a macro
;;;; (prelude.ml, "clamp is a macro and not a function"), lerp as a function
;;;; — and both are unqualified names every program already has;
;;;; examples/textures-fog-of-war.flan calls the prelude's clamp today. A
;;;; second `rl/lerp` would not even be the same function: the prelude writes
;;;; the weighted sum `(1-t)a + tb`, which returns b exactly at t = 1.0,
;;;; where raymath writes `a + t*(b - a)`, which does not once rounding is
;;;; involved. Shipping both under names one letter apart is a bug waiting
;;;; for whoever picks the wrong one. So: use the prelude's, and what is
;;;; added below is the neighbours the prelude does not have.
;; ── f32, the scalars raymath has and the prelude does not ───────────
;; Where `value` falls between `start` and `end`, as 0.0 at start and 1.0 at
;; end. raymath spells this `Normalize`, which collides with the vector
;; normalize two sections down and means something unrelated to it; it is the
;; inverse of lerp and is named for that. start = end is a division by zero,
;; as it is in raymath: an empty range has no answer and inventing one would
;; hide the caller's bug.
(defn inverse-lerp [value f32 start f32 end f32] f32
(/ (- value start) (- end start)))
;; raymath's Remap, to the character: inverse-lerp on the input range, then
;; lerp on the output range, and NOT clamped to either. A value outside the
;; input range maps outside the output range, which is what makes it usable
;; for extrapolation — a caller who wants it bounded writes the prelude's
;; clamp around it and can see that they did.
;;
;; Written as one expression rather than as (lerp out-start out-end
;; (inverse-lerp ...)) because the prelude's lerp is the weighted-sum form
;; and raymath's Remap is the a + t*(b - a) form; composing them would be a
;; different function in the last bit.
(defn remap [value f32 in-start f32 in-end f32
out-start f32 out-end f32] f32
(+ (* (/ (- value in-start) (- in-end in-start))
(- out-end out-start))
out-start))
;; raymath's Wrap. Brings a value into [min, max) by subtracting whole spans
;; of it — an angle past 2π, a scrolling offset past the tile width. floor
;; and not truncation, so a value below min wraps up instead of sticking.
(defn wrap-f32 [value f32 lo f32 hi f32] f32
(- value (* (- hi lo) (floor-f32 (/ (- value lo) (- hi lo))))))
;; ── Vector2 ─────────────────────────────────────────────────────────
(defn v2-add [a Vector2 b Vector2] Vector2
(Vector2 {.x (+ (.x a) (.x b)) .y (+ (.y a) (.y b))}))
(defn v2-sub [a Vector2 b Vector2] Vector2
(Vector2 {.x (- (.x a) (.x b)) .y (- (.y a) (.y b))}))
;; Componentwise, which is raymath's Vector2Multiply and is not a dot product
;; or anything else that deserves the word "multiply" unqualified. It is what
;; a non-uniform scale is written as.
(defn v2-mul [a Vector2 b Vector2] Vector2
(Vector2 {.x (* (.x a) (.x b)) .y (* (.y a) (.y b))}))
(defn v2-scale [v Vector2 k f32] Vector2
(Vector2 {.x (* (.x v) k) .y (* (.y v) k)}))
(defn v2-negate [v Vector2] Vector2
(Vector2 {.x (- 0.0 (.x v)) .y (- 0.0 (.y v))}))
(defn v2-dot [a Vector2 b Vector2] f32
(+ (* (.x a) (.x b)) (* (.y a) (.y b))))
;; The squared forms are not micro-optimisation dressed up: comparing two
;; distances, or a distance against a radius, is the common case and neither
;; needs the square root. raymath has both for the same reason.
(defn v2-length-sqr [v Vector2] f32
(+ (* (.x v) (.x v)) (* (.y v) (.y v))))
(defn v2-length [v Vector2] f32
(sqrt-f32 (+ (* (.x v) (.x v)) (* (.y v) (.y v)))))
(defn v2-distance-sqr [a Vector2 b Vector2] f32
(let [dx (- (.x a) (.x b))
dy (- (.y a) (.y b))]
(+ (* dx dx) (* dy dy))))
(defn v2-distance [a Vector2 b Vector2] f32
(let [dx (- (.x a) (.x b))
dy (- (.y a) (.y b))]
(sqrt-f32 (+ (* dx dx) (* dy dy)))))
;; Zero in, zero out — raymath's Vector2Normalize guards on `length > 0` and
;; returns {0, 0}, and this does the same. The alternative is dividing by
;; zero and answering a vector of NaNs, which then propagates through every
;; subsequent frame's arithmetic and reports itself somewhere else entirely.
;; The guard is the whole reason this is a function and not two divisions
;; written at the call site.
(defn v2-normalize [v Vector2] Vector2
(let [length (sqrt-f32 (+ (* (.x v) (.x v)) (* (.y v) (.y v))))]
(if (> length 0.0)
(let [inv (/ 1.0 length)]
(Vector2 {.x (* (.x v) inv) .y (* (.y v) inv)}))
(Vector2 {.x 0.0 .y 0.0}))))
;; The signed angle from a to b, in radians, via atan2 of the 2D cross
;; product over the dot. Signed and not absolute, so it says which way to
;; turn; raymath's Vector2Angle is this and not the acos form.
(defn v2-angle [a Vector2 b Vector2] f32
(atan2-f32 (- (* (.x a) (.y b)) (* (.y a) (.x b)))
(+ (* (.x a) (.x b)) (* (.y a) (.y b)))))
;; a + t*(b - a) componentwise, which is raymath's Vector2Lerp exactly. The
;; note in the file header applies: the prelude's scalar lerp is the
;; weighted-sum form and this is not, so the two do not agree in the last bit
;; at t = 1.0. raymath's is kept here because a vector path that disagrees
;; with raylib's own would be the surprise.
(defn v2-lerp [a Vector2 b Vector2 t f32] Vector2
(Vector2 {.x (+ (.x a) (* t (- (.x b) (.x a))))
.y (+ (.y a) (* t (- (.y b) (.y a))))}))
;; Counter-clockwise by `angle` radians in raylib's screen space, which has y
;; growing downward — so on screen it turns the other way from the way the
;; maths reads. raymath's Vector2Rotate, unchanged.
(defn v2-rotate [v Vector2 angle f32] Vector2
(let [c (cos-f32 angle)
s (sin-f32 angle)]
(Vector2 {.x (- (* (.x v) c) (* (.y v) s))
.y (+ (* (.x v) s) (* (.y v) c))})))
;; ── Vector3 ─────────────────────────────────────────────────────────
(defn v3-add [a Vector3 b Vector3] Vector3
(Vector3 {.x (+ (.x a) (.x b)) .y (+ (.y a) (.y b)) .z (+ (.z a) (.z b))}))
(defn v3-sub [a Vector3 b Vector3] Vector3
(Vector3 {.x (- (.x a) (.x b)) .y (- (.y a) (.y b)) .z (- (.z a) (.z b))}))
(defn v3-mul [a Vector3 b Vector3] Vector3
(Vector3 {.x (* (.x a) (.x b)) .y (* (.y a) (.y b)) .z (* (.z a) (.z b))}))
(defn v3-scale [v Vector3 k f32] Vector3
(Vector3 {.x (* (.x v) k) .y (* (.y v) k) .z (* (.z v) k)}))
(defn v3-negate [v Vector3] Vector3
(Vector3 {.x (- 0.0 (.x v)) .y (- 0.0 (.y v)) .z (- 0.0 (.z v))}))
(defn v3-dot [a Vector3 b Vector3] f32
(+ (+ (* (.x a) (.x b)) (* (.y a) (.y b))) (* (.z a) (.z b))))
;; Right-handed, which is the convention raylib's camera uses: the cross of
;; the x axis with the y axis is the z axis.
(defn v3-cross [a Vector3 b Vector3] Vector3
(Vector3 {.x (- (* (.y a) (.z b)) (* (.z a) (.y b)))
.y (- (* (.z a) (.x b)) (* (.x a) (.z b)))
.z (- (* (.x a) (.y b)) (* (.y a) (.x b)))}))
(defn v3-length-sqr [v Vector3] f32
(+ (+ (* (.x v) (.x v)) (* (.y v) (.y v))) (* (.z v) (.z v))))
(defn v3-length [v Vector3] f32
(sqrt-f32 (+ (+ (* (.x v) (.x v)) (* (.y v) (.y v))) (* (.z v) (.z v)))))
(defn v3-distance-sqr [a Vector3 b Vector3] f32
(let [dx (- (.x a) (.x b))
dy (- (.y a) (.y b))
dz (- (.z a) (.z b))]
(+ (+ (* dx dx) (* dy dy)) (* dz dz))))
(defn v3-distance [a Vector3 b Vector3] f32
(let [dx (- (.x a) (.x b))
dy (- (.y a) (.y b))
dz (- (.z a) (.z b))]
(sqrt-f32 (+ (+ (* dx dx) (* dy dy)) (* dz dz)))))
;; Zero in, zero out, exactly as v2-normalize and for the same reason.
(defn v3-normalize [v Vector3] Vector3
(let [length (sqrt-f32 (+ (+ (* (.x v) (.x v)) (* (.y v) (.y v)))
(* (.z v) (.z v))))]
(if (> length 0.0)
(let [inv (/ 1.0 length)]
(Vector3 {.x (* (.x v) inv) .y (* (.y v) inv) .z (* (.z v) inv)}))
(Vector3 {.x 0.0 .y 0.0 .z 0.0}))))
(defn v3-lerp [a Vector3 b Vector3 t f32] Vector3
(Vector3 {.x (+ (.x a) (* t (- (.x b) (.x a))))
.y (+ (.y a) (* t (- (.y b) (.y a))))
.z (+ (.z a) (* t (- (.z b) (.z a))))}))