# Recipe format

Source: https://www.ascii-magic.com/docs/recipes (ASCII Magic documentation)

A recipe is the look with no image. It is a delta against the defaults, serialised to a short string.

## The shape

`A recipe code`
```text
recipe:v1:eyJ2IjoxLCJyZW5kZXJNb2RlIjoiZGl0aGVyIn0
```

Shared as a link instead, the same payload rides as `?r=` with no prefix:

```text
https://www.ascii-magic.com/app?r=eyJ2IjoxLCJyZW5kZXJNb2RlIjoiZGl0aGVyIn0
```

A typical recipe is 120 to 280 characters, because it only carries what you changed.

## Decoding one by hand

1. Strip the `recipe:v1:` prefix. From a link, take the `r` parameter instead.
2. Right-pad with `=` until the length is a multiple of 4. The encoder strips padding.
3. Base64 decode.
4. Parse as JSON.

```bash
printf '%s' "$payload" | base64 -d | python3 -m json.tool
```

What comes back is the delta. Anything absent is at its default. It always carries `v: 1`.

## What is inside

Two parts. Top-level keys are engine settings that differ from their defaults: `renderMode`, `fontSize`, `charSet`, `coverage`, the backdrop keys, `brightness`, `contrast`, `invert`, plus a per-engine block for whichever style you used, plus lights, colour, animation, dither and depth.

A `pfx` object carries post effects, and it is a **per-property** delta rather than a whole effect block. So a recipe that only raised film grain carries `{"pfx":{"filmGrain":{"intensity":70}}}` and nothing else.

`Decoded`
```json
{
  "v": 1,
  "renderMode": "halftone",
  "fontSize": 14,
  "htAngle": 45,
  "pfx": { "filmGrain": { "enabled": true, "intensity": 70 } }
}
```

## Applying one

Applying is a **full reset then layer**, not a patch. Every key goes back to its default first, then the recipe is layered on top. So two recipes applied in sequence do not compound.

If a recipe names a Pro style and your account cannot render it, the style falls back to the free default and the rest of the recipe still applies. You get a toast saying so rather than a silent downgrade.

## Recipes inside PNGs

Every PNG the app exports carries its own recipe in a `tEXt` chunk with the keyword `asciimagicRecipe`. Other image tools ignore unrecognised chunks, so this is invisible everywhere else.

To read one by hand, find that chunk and take everything after the NUL separator. It is a normal recipe code. Dropping such a PNG back into the editor loads the look automatically.

## Two gotchas

**Non-Latin-1 characters break encoding.** The encoder uses `btoa`, so a text overlay or a custom character set containing a character above U+00FF will throw. No shipped preset contains one.

**The base64 is not URL-safe.** Padding is stripped but `+` and `/` are not translated. In practice the payload is ASCII JSON so this is rare, but a custom character set containing `?`, `>` or `~` can produce a `+`, which a query parser reads as a space. If a link misbehaves, paste the `recipe:v1:` code instead, which is parsed with a regex and is unaffected.

## A worked decode

Take the payload from a shared link and read it, with no tooling beyond a shell:

`Decode a recipe`
```bash
payload='eyJ2IjoxLCJyZW5kZXJNb2RlIjoiZGl0aGVyIn0'
# right-pad to a multiple of 4, the encoder strips padding
pad=$(( (4 - ${#payload} % 4) % 4 ))
printf '%s%s' "$payload" "$(printf '=%.0s' $(seq 1 $pad))" \
  | base64 -d | python3 -m json.tool
```

`What comes back`
```json
{
    "v": 1,
    "renderMode": "dither"
}
```

That is a complete recipe. It says the mode is `dither` and nothing else differs from the defaults, which is why it is so short. A recipe with a tuned look and a few effects runs 120 to 280 characters.

## Why it is a delta

Encoding the full state would make every recipe roughly the same size, dominated by values nobody changed, and would freeze today's defaults into every link ever shared. A delta means a recipe carries intent rather than state: it says what you changed, and everything else follows whatever the engine currently considers normal.

The trade is that a recipe is interpreted against the defaults of the build that opens it. A shipped default changing is therefore a real compatibility event, and `v` exists so that a future format change can be detected rather than silently misread.

## What a recipe does not carry

- **Your image.** A recipe is the look with no source. Opening one loads a demo so there is something to see it on.
- **Anything at its default.** If a key is absent, it is not "unset", it is normal.
- **Export settings.** Format and scale are decisions at export time, not part of the look.
- **Your account or tier.** Opening a recipe that uses a Pro style on a free account shows you the lock, not the render.

## Through the MCP server

Two tools handle the format directly, which makes an agent able to read and write looks rather than only produce them:

| Tool | Direction | Use |
|---|---|---|
| `decode_recipe` | code to settings | Read a shared look before changing it. Also the most reliable way to learn a mode's real parameter names, since a decode names every key it set. |
| `encode_recipe` | settings to code | Hand a look back to the user as a link they can open in the editor. |
| `stylize_image` with `recipe` | code to a render | Reproduce exactly. A recipe is never dressed, so this is the only call whose output is deterministic. |

## Where recipes turn up

| Place | Form |
|---|---|
| The Recipes button in the editor | A `recipe:v1:` code to copy. |
| A share link | The same payload as `?r=`, with no prefix. |
| An exported PNG | Written into a tEXt chunk, so the file remembers how it was made. |
| A project | The recipe plus the source it was made on. |
| MCP | Either tool above, or the `recipe` argument on a render. |
