Labs · Macro playground
What {{char}} and {{random}}
actually expand to — paste and see.
Card authors write macros from memory and find the typos mid-chat. Paste a greeting or prompt snippet here instead: every macro’s output highlights in place, hover shows which macro produced it, and {{random}} / {{roll}} get a re-roll button. Below the tool: a support table checked line-by-line against the engine.
Paste something with {{char}} or {{random}} — the expansion shows up here.
Macro support table
Listed against the actual engine implementation — anything marked supported can be verified in the box above, right now.
| Syntax | What it does | Support |
|---|---|---|
| {{char}} · {{character}} | Character name (V3 nickname takes priority) | ✓ Supported |
| {{user}} · {{name}} | User (persona) name | ✓ Supported |
| {{persona}} | User persona description | ✓ Supported (demo value)empty demo value here |
| {{scenario}} | Scenario | ✓ Supported (demo value)empty demo value here |
| {{model}} | Current model id | ✓ Supported (demo value)demo value here |
| {{lastMessage}} · {{lastUserMessage}} · {{lastCharMessage}} | Last message / last user message / last char message | ✓ Supported (demo value)needs chat state; placeholder here |
| {{group}} · {{charIfNotGroup}} · {{groupNotMuted}} | Group member list (falls back to char in 1-on-1) | ✓ Supported (demo value)demoed as 1-on-1 here |
| {{time}} · {{time::UTC+8}} | Current time (optional UTC offset) | ✓ Supportedbrowser clock here; device clock in the app |
| {{date}} · {{datetime}} · {{isodate}} · {{isotime}} · {{weekday}} | Date / datetime / weekday | ✓ Supported |
| {{idleDuration}} | Time since your last message | ✓ Supported (demo value)no chat state; always 'just now' here |
| {{random:a,b,c}} · {{random::a::b::c}} · {{pick}} | Pick one at random | ✓ Supported |
| {{roll:1d20}} · {{roll 1d100}} · {{roll:20}} | Dice roll (NdM; bare number = 1dN) | ✓ Supported |
| {{if::条件::甲::乙}} | Inline conditional (truthy → first branch) | ✓ Supported |
| {{newline::N}} · {{space::N}} | Newlines / spaces (with count) | ✓ Supported |
| {{noop}} · {{comment}} · {{// 注释}} | No-op / comment (expands to nothing) | ✓ Supported |
| {{reverse::文本}} | Reverse characters | ✓ Supported |
| {{setvar::k::v}} · {{getvar::k}} · {{addvar}} · {{incvar}} · {{decvar}} · {{deletevar}} · {{hasvar}} | Chat-variable family | ✓ Supportedscoped to one expansion here; persistent across turns in the app |
| {{setglobalvar::k::v}} 等 global 族 | Global-variable family | ✓ Supportedsame as above |
| {{$名字}} | Variable shorthand read | ✓ Supported |
| \{{char}} | Escape: output literally | ✓ Supported |
| {{outlet::名字}} | Pull a worldbook outlet entry | — N/A on this pageneeds worldbook context; always empty here (same shape as the runtime's 'inactive = empty') |
| {{wi::name}} · {{getarg}} | STscript closure macros | ✗ Not supportednot implemented in the app either |
| {{if}}…{{else}}…{{/if}} 块级 | Block-level conditional | ✗ Not supportedthe app ships the inline {{if::…}} form instead |
Where this tool comes from
The macro engine is ported from the expander inside the Foreverse app — an implementation aligned with the behavior subset of SillyTavern's macros/definitions: identity macros (char/user/persona), time macros (time/date/weekday with UTC offsets), random macros (every random/pick/roll form), inline {{if}}, the whole variable family (setvar/getvar/addvar/incvar plus global variants and the {{$name}} shorthand), escapes and comments. The expansion order (variable writes → variable reads → everything else) matches the app's three-pass pipeline, and representative cases were carried into this engine's self-test. Ported 2026-07.
The support table above wasn't copied from docs — every row was checked against the actual engine implementation, and anything marked supported can be verified in the input box right now.
What it can't do
Time macros ({{time}}/{{date}}) expand against your browser's clock; in the app it's the device clock. Both mean "local time" — the number you see now just won't be the number at chat time.
Chat-state macros ({{lastMessage}}, {{idleDuration}}) use placeholder values here; {{outlet}} needs worldbook activation context and stays empty. Variables live within a single expansion here — in the app, chat variables persist across turns.
FAQ
Is {{random:a,b,c}} different from {{random::a::b::c}}?
No — both separators are valid. Double-colon wins when present; otherwise commas split. Use the double-colon form when an option itself contains a comma. One option is picked with equal probability per generation — the standard trick for randomized greetings.
What's the full {{roll}} syntax?
NdM rolls N dice with M sides and sums them: {{roll:2d6}} is two six-siders. N defaults to 1 ({{roll:d20}}), a bare number is the side count ({{roll:20}} equals {{roll:1d20}}), and the space form {{roll 1d100}} works too — the common notation in skill-check cards. A malformed roll ({{roll:abc}}) doesn't error; it outputs the raw argument.
Do macros expand recursively?
No. Expansion is single-pass: if the value {{getvar}} pulls out contains {{char}}, it stays literal — both the tavern and the app engine behave this way to stop runaway nesting. The annotated view here shows it directly: macro shapes inside an expansion product stay unexpanded.
What happens to an unrecognized macro?
It's sent to the model as-is — no error, no removal. That's exactly how "the model keeps seeing curly braces" accidents happen when a card typos a macro name. This tool underlines unrecognized macros with a dotted line; scanning a card here before publishing beats discovering it mid-chat.