From 01603843c0faf9b55c94351365d75e00d46c6c36 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Fri, 11 Sep 2026 17:56:03 +0700 Subject: [PATCH] A sprite in sand, because running it is the only test there is MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sand.flan now loads a 16x8 sheet of two 8x8 brush frames and draws it four ways: the frame under the cursor through draw-texture-rec, and three badges in the corner through draw-texture, draw-texture-v and draw-texture-ex. That is not decoration — it is one call site per binding that the acceptance table cannot reach, and without it draw-texture-v and draw-texture-ex would be code nobody had ever executed. A failed load says so by name. LoadTexture on a missing file returns an id of 0, and every draw with that texture silently does nothing, so the program would look like it had a drawing bug rather than a missing file. texture-valid? is asked once at load and the answer is both printed and remembered, so the sand still runs with the cursor off. brush.png is generated rather than drawn — two circles, one ring and one filled, 102 bytes — so the repository gains an asset nobody has to keep. What this was checked by: xvfb-run, a screenshot of the running window, and the badges counted in it. Also with brush.png moved away, which is how the refusal path above is known to fire rather than merely to compile. --- NEXT.md | 24 ++++++++++++++++++++---- brush.png | Bin 0 -> 102 bytes sand.flan | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 brush.png diff --git a/NEXT.md b/NEXT.md index 7d6f7e2..74ac027 100644 --- a/NEXT.md +++ b/NEXT.md @@ -237,14 +237,30 @@ little-endian reading of the packed integer, so an identity would have passed a weaker test. That case is in the acceptance table, skipped if `libraylib` is not installed. -The bindings are 18 calls: window (`init-window`, `close-window`, +The bindings are 29 calls: window (`init-window`, `close-window`, `window-should-close?`, `set-target-fps`, `set-trace-log-level`), keyboard (`key-pressed?`/`down?`/`released?`), mouse (`mouse-button-pressed?`/`down?`/ -`released?`, `get-mouse-position`), `get-color`, and drawing (`begin-drawing`, -`end-drawing`, `draw-fps`, `clear-background`, `draw-rectangle`), plus the -`Key`, `MouseButton` and `TraceLogLevel` enums. Adding one is three lines: a +`released?`, `get-mouse-position`), `get-color`, drawing (`begin-drawing`, +`end-drawing`, `draw-fps`, `clear-background`, `draw-rectangle`), textures +(`load-texture`, `texture-valid?`, `unload-texture`, `draw-texture`, +`draw-texture-v`, `draw-texture-ex`, `draw-texture-rec`), and the shapes +texture and rectangle intersection (`set-shapes-texture`, +`get-shapes-texture`, `get-shapes-texture-rectangle`, `get-collision-rec`), +plus the `Key`, `MouseButton` and `TraceLogLevel` enums and the `Vector2`, +`Color`, `Texture2D` and `Rectangle` structs. Adding one is three lines: a `declare`, an `extern` prototype, and a one-line wrapper. +The texture calls are the first ones with no headless test, because loading +one needs a GL context. What the acceptance case does instead is pin the two +new struct layouts using the only things raylib computes from those fields +without a GPU: `GetCollisionRec`, which pins `Rectangle` completely, and +`SetShapesTexture`'s default substitution, which pins `Texture2D`'s `id` and +`format` and nothing else. Handing a struct over and reading it back proves +nothing at all — storing and returning is symmetric, so a permuted layout +comes back permuted the same way and the case passes. `width`, `height` and +`mipmaps` are therefore checked only by looking at `sand.flan` running, which +draws the brush sprite four ways for that reason. + No raylib headers are needed: `shim.c` declares the prototypes it uses, so the build depends on the shared library being linkable and not on `raylib-devel`. `vendor/raylib/link` carries `-l:libraylib.so.550` because Fedora ships the diff --git a/brush.png b/brush.png new file mode 100644 index 0000000000000000000000000000000000000000..f2e96d4e59f151e20712a46d61c2bbe02d0f80d2 GIT binary patch literal 102 zcmeAS@N?(olHy`uVBq!ia0vp^0zk~c!3HEhl+{lMQo5cljv*Ddk`oq){rIo&(BA0S zg!qIH@{-9{dnWfUlRPZ2#_ZBvhORvhg$hAT3|)U`&E)K`t^jIe@O1TaS?83{1OT9; BAfx~Q literal 0 HcmV?d00001 diff --git a/sand.flan b/sand.flan index 8671f69..eb29636 100644 --- a/sand.flan +++ b/sand.flan @@ -28,6 +28,40 @@ (import sim "sand-sim") ; no collection prefix: relative to this file (import agent "vendor:agent") ; the dev agent: redefinitions, installed below +;; The brush sprite: a 16x8 sheet of two 8x8 frames, the ring drawn while the +;; mouse is idle and the blob while it is painting. It is here because the +;; texture calls cannot be in the acceptance table at all — loading one needs a +;; GL context — so the only way they are exercised is by running this. +(defvar brush rl/Texture2D) +(defvar brush-ok bool) + +;; A missing file is not a crash and not silence: LoadTexture hands back a +;; texture with an id of 0, every draw with it is a no-op, and the program +;; looks like it has a drawing bug. So it is asked and said once, here. +(defn load-brush [] + (set brush (rl/load-texture "brush.png")) + (set brush-ok (rl/texture-valid? brush)) + (unless brush-ok + (print-line "sand: cannot load brush.png — drawing the cursor is off"))) + +;; Four draws, one per shape the call comes in, because none of them can be in +;; the acceptance table. The cursor picks one frame out of the sheet and so +;; needs a source rectangle; the three badges in the corner are the whole +;; sheet, at integer coordinates, at a Vector2 tinted with the current sand +;; colour, and scaled up — draw-texture, draw-texture-v and draw-texture-ex. +(defn draw-brush [] + (when brush-ok + (let [m (rl/get-mouse-position) + frame (f32 (if (rl/mouse-button-down? :left) 8.0 0.0))] + (rl/draw-texture-rec brush + (rl/Rectangle {:x frame :y 0.0 :width 8.0 :height 8.0}) + (rl/Vector2 {:x (- (.x m) 4.0) :y (- (.y m) 4.0)}) + rl/white) + (rl/draw-texture brush 20 50 rl/white) + (rl/draw-texture-v brush (rl/Vector2 {:x 44.0 :y 50.0}) + (rl/get-color (nth sim/colors sim/current-color))) + (rl/draw-texture-ex brush (rl/Vector2 {:x 72.0 :y 46.0}) 0.0 2.0 rl/white)))) + ;; Locals are assignable places (spec-memory.md); parameters are not. (defn paint [] (let [m (rl/get-mouse-position) @@ -61,6 +95,7 @@ (i32 (* row sim/cell-size)) sim/cell-size sim/cell-size (rl/get-color c)))))) + (draw-brush) (rl/draw-fps 20 20)) (defn main [] @@ -68,6 +103,10 @@ (rl/init-window sim/screen-width sim/screen-height "SAND") (defer (rl/close-window)) (rl/set-target-fps 120) + ;; After the window, never before: LoadTexture uploads to the GPU and there + ;; is no GPU to upload to until InitWindow has made a context. + (load-brush) + (defer (rl/unload-texture brush)) ;; The dev agent listens on a socket for redefinitions and hands them over; ;; (agent/poll) below is where they are installed. Building without --dev is ;; fine — nothing has cells to install into, so a module is refused on the