Assert audio and fonts headlessly, which both were said to be impossible
Audio was written off as needing a device. That is true of Sound and Music and false of Wave: copy, crop, reformat, export, load and decode are all CPU work, and wave-format is the same scalars-in/fields-out shape gen-image-color is, with the frame count computed rather than handed over. Cropping to a single frame before decoding puts raylib's byte-offset arithmetic in front of the decoder, which is what tells sample-size from channels — an axis discriminator, not a mirror. Fonts were said to have no headless test. They do, once the program stops asking raylib for a font and builds one out of Flan arrays: text measuring reads every field and computes. Both cases were verified red by permuting the defstructs; the permutations are recorded in the comments so the next reader need not rediscover which ones bite.
This commit is contained in:
parent
826c62a1e9
commit
34b6543e58
193
test/programs/raylib-audio.flan
Normal file
193
test/programs/raylib-audio.flan
Normal file
@ -0,0 +1,193 @@
|
||||
(import rl "vendor:raylib")
|
||||
|
||||
;; raylib's Wave family, headless — and the point of this file is that it IS
|
||||
;; headless, which the brief for this lane said audio could not be.
|
||||
;;
|
||||
;; The split inside raylib's audio module runs exactly along that line. A
|
||||
;; Sound is a buffer the miniaudio mixer owns and a Music is a decoder feeding
|
||||
;; one, so neither exists until init-audio-device has found a device. A
|
||||
;; **Wave** is samples in RAM and four integers describing them, and every
|
||||
;; call that operates on one — copy, crop, reformat, export, load, decode to
|
||||
;; floats — runs on the CPU with no device open at all. That is the Image
|
||||
;; family's situation exactly, and it gets the Image family's kind of test:
|
||||
;; raylib *computes*, and the answer is a number.
|
||||
;;
|
||||
;; Four separate claims are pinned here, and they are different claims:
|
||||
;;
|
||||
;; 1. **Wave's layout, from scalars in and fields out.** wave-format is
|
||||
;; handed three plain integers and rewrites all four u32 fields, one of
|
||||
;; them — frame-count — *computed* from the ratio of the old and new
|
||||
;; sample rates. The answers are 16, 16000, 8 and 2: all distinct, and
|
||||
;; the 16 is named by no argument at all, so a permuted defstruct has
|
||||
;; nothing to cancel against. This is gen-image-color's argument, which
|
||||
;; is the strongest shape a headless FFI test has.
|
||||
;;
|
||||
;; 2. **The file, as external ground truth.** export-wave writes a RIFF
|
||||
;; header carrying sample-rate, sample-size and channels, and a payload
|
||||
;; whose length says frame-count; load-wave reads that header back. Both
|
||||
;; ends are dr_wav's and agree with each other, not with whatever field
|
||||
;; order Flan believes in — the same reason the PNG round trip in
|
||||
;; raylib-image.flan is not the symmetric trap a store-and-return check
|
||||
;; is.
|
||||
;;
|
||||
;; 3. **`data` as a pointer, and the bytes behind it.** load-wave-samples
|
||||
;; decodes through the buffer. Move `data` up among the integers and
|
||||
;; every number in this file reads half of a pointer instead.
|
||||
;;
|
||||
;; 4. **Crop's byte arithmetic, which is the axis discriminator.** raylib
|
||||
;; crops at `init-frame * channels * sample-size/8` bytes into the
|
||||
;; buffer, so cropping one frame out of the middle and decoding it says
|
||||
;; which frame it was. That is what distinguishes sample-size from
|
||||
;; channels: exchange them and a one-frame crop lands two bytes off and
|
||||
;; the sample that comes back is a different number, not the same one
|
||||
;; mirrored. Axis-aligned geometry could never do this and neither could
|
||||
;; a round trip.
|
||||
;;
|
||||
;; The four integers describing the source wave are 8, 8000, 16 and 1 — all
|
||||
;; different, for the same reason the test image in raylib-image.flan is 4 by
|
||||
;; 2 and not square.
|
||||
|
||||
;; The samples, as bytes, little-endian 16-bit signed PCM. They are bytes
|
||||
;; rather than i16 because `data` is (Ptr u8): a wave's element width is
|
||||
;; `sample-size`, a run-time number, so no Flan type says what the buffer
|
||||
;; holds. Writing them out is the honest spelling — the file then says what it
|
||||
;; means about 16-bit PCM instead of hiding it behind a cast.
|
||||
;;
|
||||
;; 0, 1000, 2000, 3000, -3000, -2000, -1000, 0 — one cycle, every sample
|
||||
;; different from its neighbours, both signs present, so a decode that lost
|
||||
;; the byte order or the sign is a wrong number rather than merely a
|
||||
;; different one.
|
||||
(defvar pcm [16 u8])
|
||||
|
||||
(defn load-pcm []
|
||||
(set (at pcm 0) 0x00) (set (at pcm 1) 0x00) ; 0
|
||||
(set (at pcm 2) 0xE8) (set (at pcm 3) 0x03) ; 1000
|
||||
(set (at pcm 4) 0xD0) (set (at pcm 5) 0x07) ; 2000
|
||||
(set (at pcm 6) 0xB8) (set (at pcm 7) 0x0B) ; 3000
|
||||
(set (at pcm 8) 0x48) (set (at pcm 9) 0xF4) ; -3000
|
||||
(set (at pcm 10) 0x30) (set (at pcm 11) 0xF8) ; -2000
|
||||
(set (at pcm 12) 0x18) (set (at pcm 13) 0xFC) ; -1000
|
||||
(set (at pcm 14) 0x00) (set (at pcm 15) 0x00)) ; 0
|
||||
|
||||
(defconst wav-path "/tmp/flan-raylib-audio.wav")
|
||||
|
||||
(defn show-wave [name string w rl/Wave]
|
||||
(print-str name)
|
||||
(print-str " ") (print-i64 (i64 (.frame-count w)))
|
||||
(print-str " ") (print-i64 (i64 (.sample-rate w)))
|
||||
(print-str " ") (print-i64 (i64 (.sample-size w)))
|
||||
(print-str " ") (print-i64 (i64 (.channels w)))
|
||||
(newline))
|
||||
|
||||
(defn show-bool [name string b bool]
|
||||
(print-str name) (print-str " ")
|
||||
(print-line (if b "yes" "no")))
|
||||
|
||||
;; A decoded sample is compared with a tolerance and the verdict is printed,
|
||||
;; not the number. 1000 over a 15- or 16-bit full scale is 0.0305185 or
|
||||
;; 0.0305176, and this table compares stdout byte for byte at two optimisation
|
||||
;; levels, so printing the float would pin raylib's choice of divisor rather
|
||||
;; than Flan's field order. Nothing is lost: a permuted layout is not out by a
|
||||
;; rounding step, it reads a different buffer.
|
||||
(defn near? [a f32 b f32] bool
|
||||
(let [d (- a b)]
|
||||
(< (if (< d 0.0) (- 0.0 d) d) 0.0005)))
|
||||
|
||||
;; One frame out of a wave, decoded to a float.
|
||||
;;
|
||||
;; This is shaped the way it is because of a real limit in Flan, not for
|
||||
;; effect: load-wave-samples answers a (Ptr f32), a pointer cannot be indexed
|
||||
;; — `at` takes an array, a slice or a string, and there is no pointer
|
||||
;; arithmetic — so `deref` reaches sample 0 and nothing reaches sample 3. So
|
||||
;; the wave is cropped to the single frame wanted FIRST, and then sample 0 is
|
||||
;; the one being asked about.
|
||||
;;
|
||||
;; The detour pays for itself. Reading through an index would have exercised
|
||||
;; only the decoder; cropping first puts raylib's byte-offset arithmetic —
|
||||
;; init-frame times channels times sample-size over 8 — in front of the
|
||||
;; decoder, and that arithmetic reads two of the fields this file is trying
|
||||
;; to pin.
|
||||
(defn frame-at [w rl/Wave i i32] f32
|
||||
(let [one (rl/wave-copy w)]
|
||||
(rl/wave-crop (addr one) i (+ i 1))
|
||||
(let [s (rl/load-wave-samples one)
|
||||
v (deref s)]
|
||||
(rl/unload-wave-samples s)
|
||||
(rl/unload-wave one)
|
||||
v)))
|
||||
|
||||
(defn show-frame [name string w rl/Wave i i32 want f32]
|
||||
(show-bool name (near? (frame-at w i) want)))
|
||||
|
||||
(defn main [] i32
|
||||
(rl/set-trace-log-level :warning)
|
||||
(load-pcm)
|
||||
|
||||
;; ── The wave, built by hand ─────────────────────────────────────────
|
||||
;;
|
||||
;; Nothing raylib made: four integers Flan chose and a buffer Flan owns. So
|
||||
;; every number below is raylib reading THIS struct, and there is no
|
||||
;; raylib-produced struct anywhere for a permutation to hide inside.
|
||||
(let [src (rl/Wave {:frame-count 8 :sample-rate 8000 :sample-size 16
|
||||
:channels 1 :data (addr (at pcm 0))})]
|
||||
(show-bool "valid" (rl/wave-valid? src))
|
||||
(show-wave "source" src)
|
||||
|
||||
;; ── Crop ────────────────────────────────────────────────────────────
|
||||
;;
|
||||
;; Frames 2 up to 6, so four of the eight survive and nothing else
|
||||
;; changes. This is the one call that moves frame-count on its own, which
|
||||
;; is what separates that field from sample-rate: exchange the two and the
|
||||
;; crop is asked for frames 2..6 of a wave claiming 8000 of them, and the
|
||||
;; 4 lands in the sample-rate slot instead.
|
||||
;;
|
||||
;; The wave is mono, so raylib 5.5's rename of these parameters from
|
||||
;; samples to frames is not something this depends on having guessed
|
||||
;; right — at one channel the two readings coincide.
|
||||
(let [cropped (rl/wave-copy src)]
|
||||
(rl/wave-crop (addr cropped) 2 6)
|
||||
(show-wave "cropped" cropped)
|
||||
(rl/unload-wave cropped))
|
||||
|
||||
;; ── Reformat ────────────────────────────────────────────────────────
|
||||
;;
|
||||
;; The strongest case in the file. Three scalars go in — 16000 Hz, 8 bits,
|
||||
;; 2 channels — and four fields come out: 16, 16000, 8, 2. The 16 is
|
||||
;; computed, twice the original eight frames because the rate doubled, and
|
||||
;; it is the only one of the four no argument named. Eight bits rather
|
||||
;; than sixteen so that sample-size cannot be confused with the frame
|
||||
;; count it would otherwise equal.
|
||||
(let [reformatted (rl/wave-copy src)]
|
||||
(rl/wave-format (addr reformatted) 16000 8 2)
|
||||
(show-wave "reformatted" reformatted)
|
||||
(rl/unload-wave reformatted))
|
||||
|
||||
;; ── Through the pointer, one frame at a time ────────────────────────
|
||||
;;
|
||||
;; See frame-at: each of these crops to a single frame and decodes it, so
|
||||
;; raylib's byte offset into `data` is what selects the answer. Frames 1,
|
||||
;; 3 and 4 are +1000, +3000 and -3000 — three different magnitudes and
|
||||
;; both signs, so neither an offset that is out by a frame nor a decode
|
||||
;; that read the bytes backwards survives.
|
||||
(show-frame "frame 1 is +1000" src 1 0.030518)
|
||||
(show-frame "frame 3 is +3000" src 3 0.091553)
|
||||
(show-frame "frame 4 is -3000" src 4 -0.091553)
|
||||
(show-frame "frame 0 is zero" src 0 0.0)
|
||||
|
||||
;; ── Out to a file and back ──────────────────────────────────────────
|
||||
;;
|
||||
;; dr_wav writes the header from three of the four fields and the payload
|
||||
;; length from the fourth, and reads all four back out of the file. Both
|
||||
;; ends are external to Flan and agree with each other, so this is not the
|
||||
;; round trip that passes for any field order — it is the PNG argument,
|
||||
;; for audio. It also crosses a path as ptr+len.
|
||||
(show-bool "exported" (rl/export-wave src wav-path))
|
||||
(let [back (rl/load-wave wav-path)]
|
||||
(show-bool "loaded valid" (rl/wave-valid? back))
|
||||
(show-wave "loaded" back)
|
||||
;; And the samples off the file's own copy, so the bytes are checked to
|
||||
;; have survived the encoder, the decoder and the layout at once.
|
||||
(show-frame "loaded frame 1 is +1000" back 1 0.030518)
|
||||
(show-frame "loaded frame 4 is -3000" back 4 -0.091553)
|
||||
(rl/unload-wave back)))
|
||||
0)
|
||||
161
test/programs/raylib-font.flan
Normal file
161
test/programs/raylib-font.flan
Normal file
@ -0,0 +1,161 @@
|
||||
(import rl "vendor:raylib")
|
||||
|
||||
;; raylib's Font family, headless.
|
||||
;;
|
||||
;; A previous lane refused this whole family by name and gave a real reason: a
|
||||
;; Font is three ints beside a Texture2D, a Rectangle* and a GlyphInfo*, a
|
||||
;; GlyphInfo embeds an Image, and that was two more aggregates and two owned
|
||||
;; arrays "for something with no headless test at the end of it". The second
|
||||
;; half of that turned out to be wrong, and this file is the counter-example.
|
||||
;;
|
||||
;; **Nothing here makes a font.** That is the whole trick. Every raylib call
|
||||
;; that produces one needs something a headless run has not got —
|
||||
;; get-font-default needs init-window, load-font and load-font-ex need a TTF
|
||||
;; on disk and a GL context to upload the atlas to — so this file *builds* a
|
||||
;; Font out of Flan arrays, field by field, and hands it to raylib to compute
|
||||
;; with. raylib's text measuring is pure CPU arithmetic: it walks the glyph
|
||||
;; array for a codepoint, takes the advance out of the glyph or the width out
|
||||
;; of the atlas rectangle, and scales by the base size.
|
||||
;;
|
||||
;; Which makes this the scalars-in/fields-out shape rather than the
|
||||
;; store-and-return one. There is no raylib-produced struct anywhere in the
|
||||
;; file for a permutation to cancel against: Flan chose every field, and what
|
||||
;; comes back is a number raylib worked out from them.
|
||||
;;
|
||||
;; What is pinned, and by what:
|
||||
;;
|
||||
;; - **base-size** by the scale factor. Measuring "AB" at size 10 against a
|
||||
;; base size of 10 is scale 1 and at size 20 is scale 2, and both are
|
||||
;; asserted, so the field cannot be a constant that happens to fit.
|
||||
;; - **glyph-count** by the search bound. 'Z' is not in the font, and
|
||||
;; raylib's miss falls back to index 0 rather than reading past the array.
|
||||
;; - **recs and glyphs** as pointers, by being read through at all.
|
||||
;; - **advance-x against offset-x**, because glyph C deliberately has an
|
||||
;; advance of 0: raylib then falls back to the atlas rectangle's width
|
||||
;; plus offset-x, so measuring "ABC" reaches three more fields than
|
||||
;; measuring "AB" does.
|
||||
;; - **where the Texture2D sits inside the Font**, by a trap in raylib
|
||||
;; rather than a choice here: MeasureTextEx returns (0,0) at once when
|
||||
;; `texture.id` is 0. The hand-built font claims an id of 1 — there is no
|
||||
;; texture — and if the texture landed anywhere else in the struct the id
|
||||
;; would read 0 and every measurement below would collapse to zero.
|
||||
;;
|
||||
;; The numbers are chosen so that no two are equal: base size 10, glyph count
|
||||
;; 3, padding 2, advances 11 and 13, offsets 1, 2 and 3, atlas widths 5, 7
|
||||
;; and 9. Everything prints exactly — no trigonometry, so unlike the rotated
|
||||
;; camera in raylib-ffi.flan this can be compared as text.
|
||||
|
||||
;; The atlas rectangles, one per glyph. x and width differ per glyph so that
|
||||
;; a fallback to the wrong index is a wrong number; height is the same 10 for
|
||||
;; all three because raylib never reads it here.
|
||||
(defvar glyph-recs [3 rl/Rectangle])
|
||||
|
||||
;; The glyphs themselves. `image` is raylib's own pixels for the glyph and is
|
||||
;; left zeroed — it is present so the four ints in front of it are at the
|
||||
;; right offsets and so a GlyphInfo is 40 bytes rather than 16.
|
||||
(defvar glyphs [3 rl/GlyphInfo])
|
||||
|
||||
(defn build-glyphs []
|
||||
(set (at glyph-recs 0) (rl/Rectangle {:x 0.0 :y 0.0 :width 5.0 :height 10.0}))
|
||||
(set (at glyph-recs 1) (rl/Rectangle {:x 5.0 :y 0.0 :width 7.0 :height 10.0}))
|
||||
(set (at glyph-recs 2) (rl/Rectangle {:x 12.0 :y 0.0 :width 9.0 :height 10.0}))
|
||||
;; 65 66 67 are A B C. Advances 11 and 13 for the first two; C's advance is
|
||||
;; 0 on purpose, which is what sends raylib down the other branch.
|
||||
(set (at glyphs 0) (rl/GlyphInfo {:value 65 :offset-x 1 :offset-y 0
|
||||
:advance-x 11 :image (rl/Image {})}))
|
||||
(set (at glyphs 1) (rl/GlyphInfo {:value 66 :offset-x 2 :offset-y 0
|
||||
:advance-x 13 :image (rl/Image {})}))
|
||||
(set (at glyphs 2) (rl/GlyphInfo {:value 67 :offset-x 3 :offset-y 0
|
||||
:advance-x 0 :image (rl/Image {})})))
|
||||
|
||||
(defn show-bool [name string b bool]
|
||||
(print-str name) (print-str " ")
|
||||
(print-line (if b "yes" "no")))
|
||||
|
||||
(defn show-i [name string v i32]
|
||||
(print-str name) (print-str " ") (print-i64 (i64 v)) (newline))
|
||||
|
||||
(defn show-v [name string v rl/Vector2]
|
||||
(print-str name)
|
||||
(print-str " ") (print-f64 (f64 (.x v)))
|
||||
(print-str " ") (print-f64 (f64 (.y v)))
|
||||
(newline))
|
||||
|
||||
(defn show-rect [name string r rl/Rectangle]
|
||||
(print-str name)
|
||||
(print-str " ") (print-f64 (f64 (.x r)))
|
||||
(print-str " ") (print-f64 (f64 (.y r)))
|
||||
(print-str " ") (print-f64 (f64 (.width r)))
|
||||
(print-str " ") (print-f64 (f64 (.height r)))
|
||||
(newline))
|
||||
|
||||
(defn main [] i32
|
||||
(rl/set-trace-log-level :warning)
|
||||
(build-glyphs)
|
||||
|
||||
;; The font. `texture` is a lie in every field but `id`, and the id is 1 for
|
||||
;; the reason in the header note: at 0, raylib refuses to measure anything.
|
||||
;;
|
||||
;; It is built here rather than by a helper because a `defn` cannot say it
|
||||
;; returns one: the parser decides whether the form after a parameter list
|
||||
;; is a return type or the first body expression by looking the name up in
|
||||
;; the set of types THIS FILE declares, and a package's types are not in it
|
||||
;; — imports are resolved after parsing. `(defn the-font [] rl/Font ...)`
|
||||
;; therefore parses rl/Font as an expression and fails with "unknown name".
|
||||
;; That is a parser limit and not something this file wanted; it applies to
|
||||
;; rl/Vector2 just as much as to rl/Font.
|
||||
(let [f (rl/Font {:base-size 10 :glyph-count 3 :glyph-padding 2
|
||||
:texture (rl/Texture2D {:id 1 :width 32 :height 16
|
||||
:mipmaps 1 :format 7})
|
||||
:recs (addr (at glyph-recs 0))
|
||||
:glyphs (addr (at glyphs 0))})]
|
||||
;; font-valid? reads the texture id and both array pointers, so it is a
|
||||
;; null check on the three things everything below dereferences.
|
||||
(show-bool "valid" (rl/font-valid? f))
|
||||
|
||||
;; ── The search ──────────────────────────────────────────────────────
|
||||
;;
|
||||
;; A linear walk of `glyph-count` entries comparing `value`. Three hits at
|
||||
;; three different indices, and one miss: 'Z' is not in the font and
|
||||
;; raylib answers index 0 rather than running off the end. Shrink
|
||||
;; glyph-count — or land the field somewhere else — and 'C' misses too.
|
||||
(show-i "index A" (rl/get-glyph-index f 65))
|
||||
(show-i "index B" (rl/get-glyph-index f 66))
|
||||
(show-i "index C" (rl/get-glyph-index f 67))
|
||||
(show-i "index Z" (rl/get-glyph-index f 90))
|
||||
|
||||
;; ── Reading back through the two pointers ───────────────────────────
|
||||
;;
|
||||
;; The atlas rectangle for B is the second entry of `recs`, and the glyph
|
||||
;; for C is the third of `glyphs`. Both are indexed by the search above,
|
||||
;; so an exchange of the two pointers is a Rectangle read as a GlyphInfo.
|
||||
(show-rect "atlas B" (rl/get-glyph-atlas-rec f 66))
|
||||
(let [g (rl/get-glyph-info f 67)]
|
||||
(show-i "glyph C value" (.value g))
|
||||
(show-i "glyph C offset" (.offset-x g))
|
||||
(show-i "glyph C advance" (.advance-x g)))
|
||||
|
||||
;; ── Measuring, which is where the arithmetic is ─────────────────────
|
||||
;;
|
||||
;; "AB" is 11 + 13 = 24 wide and base-size tall, at scale 1.
|
||||
(show-v "measure AB" (rl/measure-text-ex f "AB" 10.0 0.0))
|
||||
|
||||
;; "ABC" adds C, whose advance is 0, so raylib falls back to the atlas
|
||||
;; width plus the offset: 9 + 3 = 12, for 36. This line and the one above
|
||||
;; disagree by exactly the amount that proves the fallback branch ran, and
|
||||
;; that branch is the only thing in the file that reads Rectangle.width
|
||||
;; out of the recs array.
|
||||
(show-v "measure ABC" (rl/measure-text-ex f "ABC" 10.0 0.0))
|
||||
|
||||
;; The same string at twice the size and a spacing of 3. Scale is 20/10,
|
||||
;; so 24 doubles to 48, and spacing is added once per gap and not once per
|
||||
;; glyph: 48 + 3 = 51. Exchange base-size and glyph-count and the scale
|
||||
;; becomes 20/3 and the search bound becomes 10 — both wrong, in different
|
||||
;; directions, on the same line.
|
||||
(show-v "measure AB big" (rl/measure-text-ex f "AB" 20.0 3.0))
|
||||
|
||||
;; One glyph, so there is no gap for the spacing to land in: 11 at scale
|
||||
;; 1, and the 3 does not appear. Without this line a spacing added per
|
||||
;; glyph rather than per gap would pass everything above.
|
||||
(show-v "measure A spaced" (rl/measure-text-ex f "A" 10.0 3.0)))
|
||||
0)
|
||||
@ -353,6 +353,121 @@ let () =
|
||||
else
|
||||
print_endline "acceptance: skipping the raylib Image case (no libraylib)";
|
||||
|
||||
(* raylib's Wave family, headless — and the first claim to make about it
|
||||
is that it exists. The received wisdom in this repository was that
|
||||
audio needs a device and so cannot be in this table at all. That is
|
||||
true of Sound, Music and AudioStream, every one of which is a handle
|
||||
the miniaudio mixer owns, and false of Wave: samples in RAM and four
|
||||
integers describing them, with copy, crop, reformat, export, load and
|
||||
decode all running on the CPU. So it gets the Image family's treatment.
|
||||
|
||||
Three shapes, and they pin different things.
|
||||
|
||||
wave-format is the scalars-in/fields-out case gen-image-color is: three
|
||||
plain integers go in and all four u32 fields come out, with
|
||||
frame-count *computed* from the ratio of the sample rates — 16, from
|
||||
eight frames at twice the rate, and no argument named it. Eight bits
|
||||
rather than sixteen in that call so sample-size cannot be confused
|
||||
with the frame count it would otherwise equal.
|
||||
|
||||
export-wave then load-wave is external ground truth, the PNG argument
|
||||
transposed: dr_wav writes the header from three fields and the payload
|
||||
length from the fourth, and reads all four back, agreeing with itself
|
||||
rather than with Flan's field order.
|
||||
|
||||
And the decoded samples are the axis discriminator this section needed.
|
||||
load-wave-samples answers a (Ptr f32), which Flan cannot index — [at]
|
||||
takes an array, a slice or a string — so the program crops to a single
|
||||
frame first and dereferences sample 0. That detour is what makes the
|
||||
case strong rather than weak: raylib's crop offset is init-frame times
|
||||
channels times sample-size over 8, so asking for frame 3 and getting
|
||||
+3000 pins sample-size against channels. Exchange those two and the
|
||||
crop lands two bytes off and the sample is a different number, not the
|
||||
same one mirrored, which is the failure mode axis-aligned geometry
|
||||
could never produce.
|
||||
|
||||
Verified red by permuting the Wave defstruct three ways: frame-count
|
||||
with sample-rate (cropped reads "8 4 16 1" and reformatted
|
||||
"16000 16000000 8 2"), sample-size with channels (every frame read
|
||||
turns to "no"), and data moved to the front (the run dies after two
|
||||
lines). *)
|
||||
let raylib_audio_out =
|
||||
"valid yes\n\
|
||||
source 8 8000 16 1\n\
|
||||
cropped 4 8000 16 1\n\
|
||||
reformatted 16 16000 8 2\n\
|
||||
frame 1 is +1000 yes\n\
|
||||
frame 3 is +3000 yes\n\
|
||||
frame 4 is -3000 yes\n\
|
||||
frame 0 is zero yes\n\
|
||||
exported yes\n\
|
||||
loaded valid yes\n\
|
||||
loaded 8 8000 16 1\n\
|
||||
loaded frame 1 is +1000 yes\n\
|
||||
loaded frame 4 is -3000 yes\n"
|
||||
in
|
||||
if Sys.command "ldconfig -p 2>/dev/null | grep -q libraylib" = 0 then begin
|
||||
outputs "raylib audio, headless" "programs/raylib-audio.flan"
|
||||
raylib_audio_out;
|
||||
outputs ~opt:"-O0" "raylib audio, headless, -O0" "programs/raylib-audio.flan"
|
||||
raylib_audio_out
|
||||
end
|
||||
else
|
||||
print_endline "acceptance: skipping the raylib Wave case (no libraylib)";
|
||||
|
||||
(* raylib's Font family, headless, which the package refused to bind at
|
||||
all until now. The stated reason was that a Font drags in two more
|
||||
aggregates and two owned arrays "for something with no headless test at
|
||||
the end of it". The generator takes all of it unchanged — a struct held
|
||||
by value is emitted after what it contains, one held by pointer is
|
||||
forward-declared — and the test turned out to exist.
|
||||
|
||||
What makes it exist is that the program does not ASK raylib for a font.
|
||||
Every call that makes one needs a window or a TTF and a GL context.
|
||||
So it builds one out of Flan arrays, field by field, and hands it over
|
||||
to be computed with: three glyphs, three atlas rectangles, a base size
|
||||
of 10 and a texture that is a lie in every field but [id]. Text
|
||||
measuring is pure arithmetic over exactly those fields, so this is
|
||||
scalars in and numbers out with no raylib-produced struct anywhere for
|
||||
a permutation to cancel against.
|
||||
|
||||
The one raylib trap worth recording: MeasureTextEx returns (0,0)
|
||||
immediately when font.texture.id is 0. The hand-built font claims an id
|
||||
of 1, and that guard is what pins where the Texture2D sits inside the
|
||||
Font — land it elsewhere and every measurement collapses to zero.
|
||||
|
||||
Glyph C carries an advance of 0 deliberately, which sends raylib down
|
||||
its other branch: the atlas rectangle's width plus the glyph's offset,
|
||||
9 + 3 = 12, which is why "ABC" is 36 and "AB" is 24. And "A" at a
|
||||
spacing of 3 measures 11 and not 14, because spacing is added per gap
|
||||
and not per glyph — without that line, a wrapper that added it per
|
||||
glyph would pass everything else.
|
||||
|
||||
Verified red by four permutations: base-size with glyph-count (the
|
||||
measurements become 80, 120, 163 and 36.6667), offset-x with advance-x
|
||||
(3, 6, 9, 1), the recs and glyphs pointers (floats in the 1e9 range and
|
||||
a garbage atlas rectangle), and moving [texture] to the end of the
|
||||
Font (the run dies after the first line). Two of the four were a crash
|
||||
rather than a wrong number, which still counts. *)
|
||||
let raylib_font_out =
|
||||
"valid yes\n\
|
||||
index A 0\nindex B 1\nindex C 2\nindex Z 0\n\
|
||||
atlas B 5 0 7 10\n\
|
||||
glyph C value 67\nglyph C offset 3\nglyph C advance 0\n\
|
||||
measure AB 24 10\n\
|
||||
measure ABC 36 10\n\
|
||||
measure AB big 51 20\n\
|
||||
measure A spaced 11 10\n"
|
||||
in
|
||||
if Sys.command "ldconfig -p 2>/dev/null | grep -q libraylib" = 0 then begin
|
||||
outputs "raylib fonts, headless" "programs/raylib-font.flan"
|
||||
raylib_font_out;
|
||||
outputs ~opt:"-O0" "raylib fonts, headless, -O0" "programs/raylib-font.flan"
|
||||
raylib_font_out
|
||||
end
|
||||
else
|
||||
print_endline "acceptance: skipping the raylib Font case (no libraylib)";
|
||||
|
||||
(* Again at -O0. Everything above runs through mem2reg, which launders a
|
||||
sloppy alloca; -O0 tests the IR actually emitted, so a disagreement
|
||||
between the two points at undefined behaviour rather than a typo. *)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user