Creation modules
An Item Code module accepts top-level variable, function, embed, media pool, interaction, action, defaults, and mixin inclusion declarations. A Boop Code module accepts top-level variable, function, embed, media pool, interaction, text, defaults, and mixin inclusion declarations. An App Main module accepts variable, function, embed, media pool, interaction, Session, named defaults, and mixin declarations.
Top-level declaration order does not affect name resolution. Actions and texts can call a function declared later, and functions can read a top-level variable declared later.
Sugar text declarations
Boops and Item actions declare each possible message with text:
text "@1 rolled {}." with Rand::pick(1..=20);
text when lucky {
show chip "Lucky roll";
"@1 found a golden ticket!"
}
text if score >= 10 {
"@1 wins!"
} else {
"@1 tries again."
}
One-line declarations end with a semicolon. A block may contain a complete program, and its final expression becomes the message. text when adds an independently eligible entry to the random text pool. text if chooses exactly one branch and requires a final else.
Interpolation is the standard way to place values in text:
text "{ACTOR.name} has {health} health.";
text "{} rolled {}." with ACTOR.name, Rand::pick(1..=20);
text "{2} challenged {1}." with rival.name, ACTOR.name;
Named placeholders accept names and property paths. Use positional formatting for expressions. Positional indexes are one-based. Write {{ or }} for a literal brace.
Values
"String"
42
-3.5
true
false
None
(1, 2, 3)
1..5
1..=5
{ name: "Key", uses: 3 }
&victory
Strings use double quotation marks. Lists use parentheses. Objects use braces with named properties. 1..5 produces 1 through 4. 1..=5 includes 5. &victory references the resource named victory in the effective Media Library.
Inside text, \n starts a new line, \t adds a tab, and \r adds a carriage return.
Names
Variable, media, and template names must begin with a letter or underscore. Remaining characters may be letters, numbers, or underscores.
score
round2
_temporaryStyle
Names are case-sensitive and can contain up to 32 characters. App variables are referenced with a $ prefix:
$playerLevel
Reserved language words, constants, and helper names cannot be used as variable names. This includes declaration words such as var, mem, const, action, text, embed, mixin, include, defaults, when, from, except, config, with, extends, and show, control words such as if, then, else, and return, constants, operators written as words, and built-in function names.
Media Pool values
const outcomes: List<Media> = (
&common,
&unusual when luck >= 40,
&rare when luck >= 80,
);
A Media Pool is an ordinary List<Media> used for random selection. Each entry may be followed by an optional when condition. The list may appear anywhere its declaration kind is allowed.
A pool composes through ordinary list expressions. + concatenates lists and - removes matching entries:
const additions: List<Media> = (
&rare when luck >= 90,
&video,
);
const special: List<Media> = outcomes + additions - (&common, &unusual);
const safe: List<Media> = outcomes - (&dangerous_result,);
These expressions create new lists and do not mutate their inputs. show media from also supports except for one Media value or a List<Media> when the exclusion only applies to that output.
PCOUNT contains the current number of participants. count() returns the same total and is usually easier to recognize when reading code. HOST contains the current Item or Boop when one is linked to the running Sugar code.
Variable declarations
A module uses one variable declaration per statement:
var score = 0;
mem var wins = 0;
var enabled: bool = true;
var mood -> "Current mood": choice = "happy" with {
input: true,
choices: ("happy", "sad", "very_excited" -> "Very excited"),
};
var reward: Item with { input: true, required: true };
var rival: Joi = None with { input: true, required: true };
var rivals: List<Joi> = () with { input: true, count: 1..=3 };
var portrait: Media = &portrait;
mem var lastUsed: Date = Date::now();
const SCORE_LABEL = format("Score: {score}");
Sugar infers string, number, and boolean values from the initial value, so str, number, and bool annotations are optional. Scalar types and choice use lowercase names. Compound types such as Item, Joi, Seat, Date, Duration, Media, Pronoun, Fn, and List<T> use PascalCase. T may be any Sugar value type, so types such as List<number>, List<str>, List<Media>, and nested lists are valid. A choice preserves the string, number, or boolean type used by its initial value and choices; every choice must use that same type. Date, Duration, Media, and Pronoun cannot use input: true. Fn values and lists containing them are execution-local and cannot be persisted.
Upper snake case is recommended for constants such as SCORE_LABEL, but it is a style convention rather than a compiler rule.
App declarations also require a scope and $ name:
player var $health = 100;
app const $eventPercent = ($eventProgress / $eventGoal) * 100;
host var $lockDifficulty = 10;
host mem var $attempts = 0;
host const $mastered = $attempts >= 5;
The leading player, app, or host keyword is the scope. It chooses where the value belongs. The declaration that follows keeps its ordinary meaning: var is mutable, mem adds persistent storage where supported, and const is calculated and read-only.
Every declaration ends with a semicolon.
The display arrow changes the visible label without changing the variable name. with configures input, validation, choices, and highlighting. A short configuration may use the inline mapping-arrow form:
var reward: Item with input => true, required => true;
The two with forms are equivalent. Block properties use :, inline properties use =>, and inline properties cannot end with a trailing comma.
Definitions
Sugar calls functions and named resource blocks definitions. Functions, actions, Automations, interactions, Sessions, embeds, chips, mixins, and named defaults are definitions. Their names share the same semantic role and function-green highlighting, whether or not they accept parameters.
Item action declarations
action rollNumber -> "Roll a number" when enabled {
config {
description: "Rolls a die for up to three participants.",
participants: 1..=3,
consumes: 1,
}
mem var rolls = 0;
chip rollCount {
config highlighted => true;
format("Rolls: {rolls}")
}
let roll = Rand::pick(1..=20);
rolls++;
text "@1 rolled {roll}.";
}
An action identifier is required. The display arrow sets the name people see. when controls whether the action is available. Inside config, description is optional static text with an 80-character limit, participants accepts one whole number or a range, and consumes sets how many Item uses a successful action spends.
The config block is optional. An action uses one participant by default. It consumes one use on a consumable Item and zero uses on a persistent Item. Only write the settings that need to differ from those defaults.
A short action configuration may use the inline form:
config description => "Rolls a die.", participants => 1..=3, consumes => 1;
Block settings use :, while inline settings use =>. Separate inline settings with commas and end the configuration with a semicolon. Automation configurations support the same two forms.
Variables declared inside an action belong to that action. Executable code directly in the action runs once before Sugar evaluates its texts. Action let values are available to text conditions and bodies. An action needs at least one text declaration.
Statements
Separate ordinary statements with semicolons:
score += 5;
wins++;
"Score: {score}"
Line breaks are optional. They improve readability but do not replace required semicolons between ordinary statements.
All show output kinds accept a final when condition:
show chip "Ready" when enabled;
show media from outcomes when visible;
Assignments
score = 10;
score += 5;
score -= 2;
score *= 3;
score /= 2;
score++;
score--;
++score;
--score;
Assignments work in Item action and Boop programs and in Automations that are allowed to change the variable. Conditions and const declarations cannot use assignments.
Temporary variables
let roll = Rand::pick(1..=20);
let enemy: Seat = SEATS.find(|seat| seat != SELF);
let creates a temporary variable that lasts only while the current program runs. It accepts the same lowercase scalar and PascalCase compound type annotations. Temporary declarations count toward the program’s overall instruction limit.
Session actions
An App Session declares shared state, per-seat state, and an optional read-only composer view:
session arena {
config seats => 2..=4;
var turn = 1;
seat var health <- $health;
view {
show chip format("Turn {}", turn);
show embed scoreboard;
}
}
The view must be last and can show chips and App embeds. It does not mutate state or create message interactions.
An action opts into one App Session explicitly:
action observe {
use session observatory;
text "Turn {turn}.";
}
Only that action loads SELF, SEATS, SESSION, and the Session’s state. Ordinary actions do not query Session state.
Statement-form decisions
if score >= 10 {
wins++;
"You win!"
} else if score == 9 {
"Almost!"
} else {
losses++;
"Try again."
}
The else branch is optional. Add else if branches for more conditions.
Value-form decisions
if condition then valueWhenTrue else valueWhenFalse
Use the value form inside a larger expression:
format("Status: {}", if health > 0 then "READY" else "DEFEATED")
Match expressions
match evaluates its subject once, checks arms from top to bottom, and returns the first matching arm’s value:
let result = match roll {
1 => "Critical failure",
2 | 3 => "Close call",
4..=19 => "Success",
20 => "Perfect",
_ => "Unexpected roll",
};
A value-form match may initialize a var or const declaration or appear inside another expression. Its subject is evaluated once, and only the selected arm’s value is evaluated.
Supported patterns are number, text, boolean, and None literals; numeric .. and ..= ranges; fixed-list patterns; alternatives joined by |; and the _ catch-all pattern. A fixed-list pattern requires the subject list to have the same length, then recursively checks each position:
match (species, personality) {
("SULCATA", "CURIOUS" | "DETERMINED") => "ACTIVE",
("SULCATA", _) => "RELAXED",
_ => "NONE",
}
Structural patterns compare values and do not bind new variables. _ must be last when it is the complete arm pattern; _ inside a fixed list matches any value at that position. A match without a final _ is only complete when its boolean patterns cover both true and false.
An arm can contain a program inside braces. The program’s final expression becomes the arm’s value.
The program’s final value
The final expression becomes the program’s text result. Earlier calculations do not display automatically. A show statement can add another visible result on an earlier line.
score += 5;
"Score: {score}"
Embed declarations
Embed Templates are top-level declarations in Item or Boop Code and App Main:
embed stats -> "Stats" when visible {
description: format("Score: {}", score),
fields: (
score -> "Score" with icon => &score_icon,
),
}
The optional display arrow supplies the embed’s title. Without it, the declaration name is not shown as a title. A when condition makes the embed add nothing when its condition is false. Parameters follow the embed name, and show embed supplies their arguments:
embed stats(score) -> "Stats" {
fields: (score -> "Score"),
}
show embed stats(score);
A local Item or Boop embed may use extends appEmbed to inherit an App embed. It declares only its additional parameters. The complete call receives inherited arguments first, then the child’s arguments.
Embed declaration fields accept an optional image with either with icon => &resource or with { icon: &resource }. The same field syntax is available in a declaration’s addFields list.
An Item embed accepts highlighted: true. Up to two highlighted embeds are evaluated from saved Item state and displayed in the Item dialog. Every parameter of a highlighted embed must have a default.
Chip declarations
Items and Boops can name a reusable chip at the top level. Item actions may also declare their own chips:
chip healthStatus(value: number = health) {
config icon => &heart;
format("Health: {}", value)
}
show chip healthStatus;
show chip healthStatus(50);
The optional parameters follow function parameter rules. Put config first when the chip needs an icon, progress, or highlighted setting. Highlighting is available only to parameterless Item chips and displays the chip in the Item dialog. An Item can have up to eight highlighted chips across its top level and actions.
Function declarations
Items and Boops define reusable functions at the top level of Code. Apps define them at the top level of Main:
fn greet(name, punctuation = "!") {
format("Hello, {}{}", name, punctuation)
}
name is a required parameter. punctuation is optional because it has a default value. Required parameters must appear before parameters with defaults.
The final expression is returned automatically. return value; returns early, and return; stops the function with a None result.
Functions may call functions declared later in the same module. Direct and indirect recursion are not allowed. Creator function names cannot replace built-in Sugar function names.
Only App functions may use the host keyword. A function declared this way is called a hosted function:
host fn status() {
format("{} HP", health)
}
A regular App function sees App resources. A hosted function can also use the variables and resources of the Item or Boop that calls it.
Defaults declarations and applications
Named defaults are declared at the top level of App Main:
defaults tropical {
$hydration = 85;
$waterAmount = 15;
}
Apply them at the top level of a linked Item or Boop Code module:
defaults tropical;
A block may extend a named configuration or provide inline overrides:
defaults tropical {
$hydration = 95;
}
defaults {
$waterAmount = 25;
}
Assignments inside defaults blocks end with semicolons. The block itself does not. Only mutable variables in the Host scope may be configured. Applications are resolved from top to bottom, so later assignments win.
Mixin declarations and inclusion
Mixins are declared at the top level of App Main:
mixin water(amount = 20) {
$hydration = clamp($hydration + amount, 0, 100);
}
An inclusion is replaced by the mixin’s body before the linked creation is validated:
include water(25);
Every include statement ends with a semicolon. Parentheses may be omitted when a mixin has no parameters: include commonActions;.
Required parameters must appear before parameters with defaults. Arguments that can be resolved while parsing may shape declarations. Runtime expressions are evaluated once at the inclusion site and can only be used where executable statements are valid.
Mixins are contextual. Their expanded body must be valid where it appears. They may contribute complete top-level declarations or code inside an action, text, function, or Automation. Mixins can include other mixins, but direct and indirect recursion are rejected.
Comments
// starts a comment that continues to the end of its line:
// Keep the roll so both parts of the message use the same number
let roll = Rand::pick(1..=20);
"First roll: {roll}, second mention: {roll}"
Sugar ignores comments when it runs the code. JOI keeps them in the saved source. Block comments are not supported.