From 0a7d530dbd9d7dc1226549e35a11acc1abccca9f Mon Sep 17 00:00:00 2001 From: Jonathan James Date: Sun, 19 Apr 2026 21:22:06 +0000 Subject: [PATCH] docs(reference): align meme section with implementation Walking the reference section end-to-end against the built runtime turned up several documented features that either don't exist or crash when invoked as shown. This commit corrects the inaccurate claims and adds features that are implemented but undocumented. Fiction removed: - docs/reference/src/meme/styles.md previously listed 'built-in style keywords' (impact, cinematic, shout, whisper, panic). None of these exist in the codebase; the parser rejects them. Replaced with an accurate inline-style section that also documents the dimensions-required rule for block form. - docs/reference/src/meme/effects.md called blur 'Gaussian' but the implementation is a box blur. - docs/reference/src/meme/effects.md advertised tint(hexColor) taking a string; the implementation requires a numeric parameter, hardcodes alpha at 0.5, and has no 0x-literal syntax to pass a hex int. Replaced the row with a warning note until the effect is fixed. - docs/reference/src/meme/gif.md listed 'bounce' as an easing option, and CLAUDE.md also listed it. The parser accepts it but MacTimeline::applyEasing silently falls through to linear. Removed from both places until wired up. - docs/reference/src/meme/gif.md's day/night example piped through tint("#000044AA") which aborts the interpreter. Replaced with vignette |> brightness(0.5). - docs/reference/src/meme/gif.md's animate example used animate(frames, 500) which aborts with std::bad_variant_access (AnimateFunction expects a Duration instance). Corrected to animate(frames, Duration(500)) and expanded into a proper 'Programmatic GIFs' section with a reduce(Gif()) alternative for per-frame durations. - CLAUDE.md stated the GIF frame cap was 200 in two places. The actual cap (GifLimits.h MAX_GIF_FRAMES) is 500. Corrected. Features added to the reference: - docs/reference/src/meme/meme_literal.md expanded the 'With Positioned Text' section to show multiple entries, fontSize with numeric and tier forms, and the shared-style caveat. - docs/reference/src/meme/meme_literal.md added a new 'Meme Assets' section listing all nine @meme. assets. Previously undocumented. - docs/reference/src/meme/grid.md added 'Spread from an Array' covering 'grid 2x2 { ...arr }' and bare 'grid arr' forms. - docs/reference/src/meme/grid.md added 'Programmatic Composition' covering beside(a, b), stack(a, b), and toGrid(arr, cols, rows) -- all previously undocumented despite being in the public registry. - docs/reference/src/meme/gif.md added an 'Operational Limits' section documenting the 500-frame cap, the 15 fps internal transition rate (so agents stop using 200ms transitions and wondering why they look stepped), and the 4096x4096 dimension clamp. All code examples in the changed docs were run end-to-end against the built interpreter. No runtime code changed; all 91 existing tests pass. Relative links within meme/*.md were checked and resolve cleanly. --- CLAUDE.md | 6 ++--- docs/reference/src/meme/effects.md | 12 ++++++--- docs/reference/src/meme/gif.md | 27 +++++++++++++++++--- docs/reference/src/meme/grid.md | 33 +++++++++++++++++++++++++ docs/reference/src/meme/meme_literal.md | 31 +++++++++++++++++++++-- docs/reference/src/meme/styles.md | 19 ++++++-------- 6 files changed, 105 insertions(+), 23 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index e9cce05..5fafb91 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -111,7 +111,7 @@ Compose: `effect name = sepia >> contrast(1.5) >> vignette;` Types: `crossfade`, `slideLeft`, `slideRight`, `slideUp`, `slideDown`, `wipe`, `fadeBlack`, `zoom` -Easing: `linear`, `easeIn`, `easeOut`, `easeInOut`, `bounce` +Easing: `linear`, `easeIn`, `easeOut`, `easeInOut` Syntax: `--- crossfade 300ms easeInOut ---` between gif entries @@ -194,7 +194,7 @@ Version lives in the `VERSION` file (single source of truth). CMake injects it a - `MAC_OUTPUT_DIR` env var overrides default output directory (`~/mac/output/`) - Output paths are sanitized (directory components stripped) to prevent path traversal - Template resolution validates canonical paths stay under script/binary directories -- Max image dimensions clamped to 4096x4096, max GIF frames capped at 200 +- Max image dimensions clamped to 4096x4096, max GIF frames capped at 500 ## Code Style @@ -222,7 +222,7 @@ Version lives in the `VERSION` file (single source of truth). CMake injects it a | `include/MacEnum.h` | Enum sum type (MacEnumDef, MacEnum) | | `include/MacLambda.h` | Lambda/closure with tail expression support | | `include/MacMeme.h` | Template map, resolveTemplate, path traversal protection | -| `include/MacGif.h` | GIF frame storage, frame limit (200 max) | +| `include/MacGif.h` | GIF frame storage, frame limit (500 max) | | `include/GifLimits.h` | `MAX_GIF_FRAMES` constant | | `include/MacAnalyzer.h` | Semantic analyzer entry point | | `include/AnalyzerWalk.h` | AST walk for analysis (match binding scope fix) | diff --git a/docs/reference/src/meme/effects.md b/docs/reference/src/meme/effects.md index 270e288..beb1ff1 100644 --- a/docs/reference/src/meme/effects.md +++ b/docs/reference/src/meme/effects.md @@ -38,7 +38,7 @@ These take a numeric argument and return a function that transforms a meme. | Effect | Parameter | Description | |--------|-----------|-------------| -| `blur(radius)` | 1-20 | Gaussian blur | +| `blur(radius)` | 1-20 | Box blur | | `pixelate(blockSize)` | 2-50 | Pixelation | | `noise(amount)` | 0.0-1.0 | Random noise overlay | | `saturate(factor)` | 0.0-5.0 | Color saturation (1.0 = normal) | @@ -49,12 +49,18 @@ These take a numeric argument and return a function that transforms a meme. | `posterize(levels)` | 2-32 | Reduce color levels | | `chromatic(offset)` | 1-20 | RGB channel displacement | | `threshold(level)` | 0-255 | Black/white binarization | -| `tint(hexColor)` | hex string | Color tint overlay | | `jpeg(quality)` | 1-100 | JPEG compression artifacts | +> `tint()` is currently not usable from Mac source code. It exists in the +> registry but requires a numeric parameter and hardcodes alpha to 0.5; +> the previously-documented `tint("#RRGGBBAA")` form rejects strings. +> Until this is fixed, prefer `hueShift`, `saturate`, or a `background` +> color on a style for similar effects. See the tracking issue for +> `tint()` before using it. + ``` @blank "Blurry" |> blur(5) => "blurred.png"; -@blank "Warm" |> tint("#FF880044") => "tinted.png"; +@blank "Posterized" |> posterize(4) => "poster.png"; ``` ## Named Effects diff --git a/docs/reference/src/meme/gif.md b/docs/reference/src/meme/gif.md index f0cd914..30d58fd 100644 --- a/docs/reference/src/meme/gif.md +++ b/docs/reference/src/meme/gif.md @@ -87,7 +87,6 @@ gif { | `easeIn` | Accelerate from rest | | `easeOut` | Decelerate to rest | | `easeInOut` | Accelerate then decelerate | -| `bounce` | Bouncing effect at end | ### Loop-Back Transition @@ -138,7 +137,7 @@ Frames with transitions can also have effects: gif { @blank "Day" : 2s --- crossfade 500ms --- - @blank "Night" |> tint("#000044AA") |> vignette : 2s + @blank "Night" |> vignette |> brightness(0.5) : 2s } => "day_night.gif"; ``` @@ -156,7 +155,9 @@ gif { ## Programmatic GIFs -Use the `animate()` function to create GIFs from arrays: +Use the `animate()` function to create GIFs from an array of frames where +every frame has the same duration. The duration argument must be a +`Duration` instance, not a raw number. ``` var frames = [ @@ -164,7 +165,15 @@ var frames = [ @blank "Two", @blank "Three" ]; -animate(frames, 500) => "uniform.gif"; +animate(frames, Duration(500)) => "uniform.gif"; +``` + +For GIFs with per-frame durations, build up a `Gif` with `reduce`: + +``` +var gif = frames + |> reduce((g, m) -> g.frame(m, Duration(500)), Gif()); +gif.save("uniform.gif"); ``` ## Looping @@ -178,6 +187,16 @@ GIFs loop infinitely by default. - Cannot be placed inside grids or other frames - Cannot be nested inside other gifs +## Operational Limits + +- **Frame cap: 500 total frames per GIF.** This includes transition + frames, which are rendered at 15 fps. A 500ms `crossfade` between two + keyframes contributes ~7 interpolated frames on top of the keyframes + themselves. Exceeding 500 frames raises `GIF frame limit exceeded`. +- **Transition frame rate: 15 fps.** A 200ms transition is only 3 frames + and will look stepped; target 400-800ms for smooth transitions. +- **Image dimensions capped at 4096x4096.** Larger values are clamped. + ## See Also - [Saving Output](./save.md) diff --git a/docs/reference/src/meme/grid.md b/docs/reference/src/meme/grid.md index 7fb303b..02139c9 100644 --- a/docs/reference/src/meme/grid.md +++ b/docs/reference/src/meme/grid.md @@ -72,6 +72,39 @@ grid 1x2 { } => "nested.png"; ``` +## Spread from an Array + +When the entries are already in an array, use `...` to spread them into +the grid body, or omit the body entirely and let `grid` derive the shape +from the array's length. + +``` +var memes = data |> map(d -> @two_panel { top: d[0] bottom: d[1] }); + +grid 2x2 { ...memes } => "explicit.png"; // explicit shape +grid memes => "auto.png"; // auto-dimensioned +``` + +If an explicit `COLSxROWS` is given and the array size doesn't match, +extras are truncated and missing cells are filled with blank. + +## Programmatic Composition + +For cases where a literal `grid { }` block is awkward, three native +functions compose memes without syntax sugar: + +| Function | Description | +|----------|-------------| +| `beside(a, b)` | Place two memes side by side | +| `stack(a, b)` | Stack two memes vertically | +| `toGrid(arr, cols, rows)` | Arrange an array of memes into a grid | + +``` +beside(@blank "before", @blank "after") => "compare.png"; +stack(@blank "setup", @blank "punchline") => "bit.png"; +toGrid(memes, 3, 2) => "dashboard.png"; +``` + ## Composition Type `grid` produces a frame type (Meme), which means it can be used inside: diff --git a/docs/reference/src/meme/meme_literal.md b/docs/reference/src/meme/meme_literal.md index de31472..23b2a73 100644 --- a/docs/reference/src/meme/meme_literal.md +++ b/docs/reference/src/meme/meme_literal.md @@ -100,14 +100,41 @@ Pipe memes through effect functions. ## With Positioned Text -Use `x` and `y` for absolute text positioning. +Use `text:` followed by `x:` and `y:` to place text at absolute pixel +coordinates. Multiple positioned entries are allowed and can mix with +the named positions (`top:`, `bottom:`, `center:`). ``` @blank 720x720 { - text: "Anywhere" x: 100 y: 200 + top: "HEADER" + text: "label A" x: 120 y: 200 fontSize: "sm" + text: "label B" x: 520 y: 200 fontSize: 64 + bottom: "footer" } ``` +`fontSize` accepts either a pixel number or one of the size tier strings +`"sm"`, `"md"`, `"lg"`, `"xlg"`. If omitted, the meme's default sizing +applies. + +All positioned text on a single meme shares the meme's outer style +(color, outline, weight, etc.). Per-entry colors are not currently +supported — use `beside`, `stack`, or `grid` to combine memes with +different styles. + +## Meme Assets + +The `@meme.` syntax resolves to a built-in meme asset image. + +``` +@meme.shrek_smirk "When your code compiles" => "shrek.png"; +@meme.girl_side_eye { bottom: "QA finds a bug" } => "qa.png"; +``` + +Available assets: `shrek_smirk`, `shrek_side_eye`, `girl_side_eye`, +`king_bach_stare`, `jordan_crying`, `kid_crying`, `window_despair`, +`guy_crying`, `idk_about_that`. + ## See Also - [Styles](./styles.md) -- text color, outline, font diff --git a/docs/reference/src/meme/styles.md b/docs/reference/src/meme/styles.md index b4226b7..6284c81 100644 --- a/docs/reference/src/meme/styles.md +++ b/docs/reference/src/meme/styles.md @@ -80,21 +80,18 @@ style subtitle { ## Inline Style -Styles can also be applied using keyword modifiers after the template: +Styles can be applied after the template (and after any dimensions): ``` -@blank cinematic { top: "Movie Style" } -``` +// Quick form: dimensions are optional +@blank cinematic "Movie Style" => "movie.png"; -Built-in style keywords: +// Block form: dimensions are required when a style is used +@blank 720x720 cinematic { top: "Movie Style" } => "movie.png"; +``` -| Keyword | Effect | -|---------|--------| -| `impact` | White text, black outline, bold, uppercase | -| `cinematic` | Letter-boxed look | -| `shout` | Large bold uppercase | -| `whisper` | Small, normal weight | -| `panic` | Red text, heavy outline | +A style must be defined with `style name { ... }` before it can be +referenced this way. There are no pre-defined style keywords. ## See Also