Logic Reference β
Complete syntax reference for ISDL logic constructs, functions, and expressions.
Variables β
Variable Declaration β
fleeting <name> = <value> // Mutable variable
eternal <name> = <value> // Immutable constantVariable Types β
Variables can hold any value type:
fleeting number = 42
fleeting text = "Hello"
fleeting boolean = true
fleeting array = [1, 2, 3, 4]
fleeting roll = roll(2d6)Array Access β
fleeting value = array[index] // Get element at index
fleeting dynamic = array[variable] // Use variable as indexMathematical Operations β
Arithmetic Operators β
| Operator | Description | Example |
|---|---|---|
+ | Addition | 5 + 3 |
- | Subtraction | 10 - 4 |
* | Multiplication | 6 * 2 |
/ | Division | 15 / 3 |
Assignment Operators β
| Operator | Description | Example | Equivalent |
|---|---|---|---|
+= | Add and assign | x += 5 | x = x + 5 |
-= | Subtract and assign | x -= 3 | x = x - 3 |
*= | Multiply and assign | x *= 2 | x = x * 2 |
/= | Divide and assign | x /= 4 | x = x / 4 |
++ | Increment | x++ | x = x + 1 |
-- | Decrement | x-- | x = x - 1 |
The wiki examples prefer x += 1 and x -= 1 over x++ and x-- because they read more clearly for newer devs. Both forms produce identical generated output.
Mathematical Functions β
| Function | Description | Example |
|---|---|---|
Math.abs(value) | Absolute value | Math.abs(-5) β 5 |
Math.ceil(value) | Round up | Math.ceil(4.3) β 5 |
Math.floor(value) | Round down | Math.floor(4.7) β 4 |
Math.round(value) | Round to nearest | Math.round(4.6) β 5 |
Math.max(a, b, ...) | Maximum value | Math.max(5, 10, 3) β 10 |
Math.min(a, b, ...) | Minimum value | Math.min(5, 10, 3) β 3 |
Math.random() | Random 0-1 | Math.random() β 0.747... |
Comparison Operators β
ISDL provides word-style operators (preferred β they read like English) and symbolic alternatives (familiar to programmers). Both work; the wiki examples use the word forms.
| Operator | Description | Example |
|---|---|---|
equals | Equal to (preferred) | x equals "text" |
!equals | Not equal to (preferred) | x !equals "text" |
== | Equal to (alternative) | x == 5 |
!= | Not equal to (alternative) | x != 5 |
< | Less than | x < 5 |
> | Greater than | x > 10 |
<= | Less than or equal | x <= 5 |
>= | Greater than or equal | x >= 10 |
has | Contains value | self.Tags has "magic" |
excludes | Does not contain value | self.Tags excludes "cursed" |
startsWith | Starts with text | self.Name startsWith "Sir" |
endsWith | Ends with text | self.Title endsWith "III" |
Logical Operators β
| Operator | Description | Example |
|---|---|---|
and | Logical AND | a > 5 and b < 10 |
or | Logical OR | a == 1 or b == 2 |
! | Logical NOT | !isAlive |
Existence & Content Checks β
| Operator | Description | Example |
|---|---|---|
exists | Value exists | self.OptionalField exists |
!exists | Value doesn't exist | self.OptionalField !exists |
isEmpty | String or array is empty | self.Notes isEmpty |
isNotEmpty | String or array has content | self.Inventory isNotEmpty |
Conditional Statements β
If Statement Syntax β
if (condition) {
// statements
}
else if (condition) {
// statements
}
else {
// statements
}Ternary Operator β
A compact inline conditional β returns one of two values based on a condition. Works anywhere an expression is valid: value: blocks, actions, function bodies.
fleeting result = condition ? valueIfTrue : valueIfFalse
// Inside a value: block
readonly number Tier(value: { return self.Level >= 10 ? 2 : 1 })
// Inside an action
fleeting label = self.HP > 0 ? "Alive" : "Defeated"The full condition is evaluated first, so a + b > c ? x : y parses as (a + b > c) ? x : y.
Functions β
Function Definition β
function <name>(<parameters>) returns <returnType> {
// function body
return <value>
}Parameter Syntax β
function name(type paramName) returns returnType { } // Required parameter
function name(type paramName = defaultValue) returns type { } // Default parameterReturn Types β
number- Numeric valueboolean- True/false valuestring- Text valuenothing- No return value (void)
Function Call β
self.functionName(parameters)Property Access β
Self Properties β
self.PropertyName // Direct property access
self[self.dynamicProperty] // Dynamic property lookup
self.property.subProperty // Nested property accessParent Properties β
Requires a type guard.
parent.*only resolves inside anif (parent is SomeActor) { β¦ }block, since an Item can be owned by any Actor type. Without it you'll get "Could not resolve reference to Property".
if (parent is Hero) {
parent.PropertyName // Parent document property
parent[self.dynamicProperty] // Dynamic parent lookup
parent.property.subProperty // Nested parent access
}Target Properties β
target.PropertyName // Target document property
target.property.subProperty // Nested target accessSpecial Self Properties β
self.Name // Document name
self.Description // Document description
self.Image // Document image
self.DocumentType // Document type (actor/item)
self.EditMode // Current edit mode
self.Effects // Active effectsSystem Properties β
User Properties β
User.isGM // Boolean: Is user a GM?
User.name // String: User's nameCombat Properties β
Combat.isMyTurn // Boolean: Is it this character's turn?
Combat.isNotMyTurn // Boolean: Is it NOT this character's turn?Combat Methods β
Combat.nextTurn() // Advance to next turn
Combat.end() // End combatDice Rolling β
Roll Syntax β
roll(diceExpression)
roll(diceExpression, param: value, ...) // with detection params (below)Dice Expression Examples β
roll(d20) // Single d20
roll(2d6) // Two d6 dice
roll(3d8 + 5) // Three d8 plus 5
roll(d20 + self.Modifier) // d20 plus character modifier
roll(self.DiceCount + "d6") // Dynamic dice countDetection Parameters β
Optional parameters configure crit/fumble flagging and success counting at roll time. They are opt-in β if you don't add the parameter, the matching accessor isn't available.
roll(d20 + self.STR, crit: 20, fumble: 1) // natural 20 crits, natural 1 fumbles
roll(d20 + self.STR, crit: >= 19) // crit range (19β20)
roll(5d6, success: >= 5) // count dice β₯ 5 as successes
roll(5d6, success: >= 5, failure: 1) // 1s subtract from the success count| Parameter | Meaning |
|---|---|
crit: / fumble: | Compare the natural face of the first die. Bare value (crit: 20) means equality; an operator form (crit: >= 19) uses that operator. |
success: / failure: | Per-die comparison for success counting. failure: subtracts from the success total. |
For total-based crits (compare the modified total, not the die face), branch manually: if (myRoll.total >= 25) { ... }.
Roll Properties β
fleeting attack = roll(d20 + self.STR, crit: 20, fumble: 1)
attack.total // Total result (also the value of a bare `attack`)
attack.crit // true if the crit: condition triggered (read/write β see below)
attack.fumble // true if the fumble: condition triggered (read/write β see below)
fleeting pool = roll(5d6, success: >= 5, failure: 1)
pool.successes // number of successes (requires success:)
pool.highest // highest face rolled
pool.lowest // lowest face rolled
pool.dice // array of face values β iterable with `each`
pool.count(6) // how many dice show a 6
pool.count(face => face >= 5) // how many dice satisfy a predicate
pool.contains(1) // true if any die shows a 1
pool.contains(face => face >= 5)count/contains accept either a face value or a single-parameter predicate (face => ...). The predicate variable can be any name except die/dice, which are reserved field-type keywords β use face, d, etc.
pool.dice, count, contains, highest, and lowest see every standing die, including dice dropped by keep/drop modifiers β so roll((n)d6 kh1) plus .contains(1) still detects a 1 on a discarded die (the EZD6 "spell fizzles" pattern).
Marking Crit/Fumble Manually β
When a rule is too complex for a crit:/fumble: threshold, set the flags yourself β crit/fumble are read/write. A manual value wins over the parameter, and you don't need a crit:/fumble: parameter to set them.
fleeting wild = roll(2d6)
if (wild.contains(6)) { wild.crit = true } // crit on any 6
if (wild.contains(1)) { wild.fumble = true } // fumble on any 1
// "nothing" / reset: wild.crit = falseThe chat card auto-highlights from the flags either way. If a roll ends up both crit and fumble (only really possible via manual marking), it gets a distinct rare "crit-fumble" treatment (a greenβred shimmer). .successes is the exception β it's computed from success: and can't be set manually.
The chat card automatically highlights a crit (green) or fumble (red) when a crit:/fumble: parameter is present and triggers.
// EZD6-style fizzle:
fleeting cast = roll((self.Dice)d6 kh1)
if (cast.contains(1)) {
chat Spell { "The spell fizzles!" }
}
// Walk every die:
each face in cast.dice {
log("Rolled a " + face)
}Damage Rolls β
damage(roll: <expression>, type: <damage type>) produces a typed damage roll. The result is a roll-like object that carries both the rolled total and the damage type metadata (color, icon, custom flags from choice<damageType>). Use this when you want a single value that knows what kind of damage it represents β useful for chat-card display, resistance lookups, and Active Effect bonuses keyed to damage types.
fleeting hit = damage(roll: 2d8 + self.STR, type: self.SpellDamage)
if (target.hasResistance(hit.type)) {
hit = hit / 2
}
chat AttackResult {
"Deals " + hit + " " + hit.type + " damage!"
tag hit.icon // metadata from the choice<damageType> entry
}Parameters:
roll:- The dice expression for the damage amount (same syntax asroll(...)).type:- The damage type. Typically achoice<damageType>field reference (e.g.self.SpellDamage), but a literal string ("Fire") is also accepted.
Properties on the returned object include the standard roll properties (.total, .dice) plus all metadata attached to the chosen damage-type choice (.color, .icon, plus any custom keys you defined on the choice<damageType>).
Chat Cards β
Chat Card Syntax β
chat <name> {
<line>
<line>
...
}
// Or with a custom template:
chat <name>(template: "path/to/template.hbs") {
<line>
...
}A chat block is zero or more lines. Lines render in source order; ISDL does not enforce ordering between flavor, tag, body, and roll lines (the conventional layout is flavor at the top, body and roll variables in the middle, tag at the bottom).
Custom Card Templates β
The optional template: parameter overrides the default chat-card HTML with a Handlebars template path of your choosing. Use this when you need card layouts that the line-based grammar can't express (multi-column tables, custom buttons, etc.). The path is resolved relative to your system's root.
chat AttackResult(template: "templates/chat/attack-card.hbs") {
flavor "You strike!"
attackRoll
damage
}When you provide a custom template, the chat-block lines are still evaluated and made available to the template under the same names you'd use in any chat block β but how they render is up to your Handlebars file.
Valid Line Forms β
| Line form | Renders as | Example |
|---|---|---|
| Plain expression | A line of body text. Strings render literally; expressions evaluate and convert to text. | "Hit for " + damage + "!" |
| Roll variable | The full roll inline with an expandable formula breakdown. (Only place where the roll object renders, not its total.) | attackRoll |
flavor <expression> | Flavor text at the top of the card. | flavor "You strike!" |
tag <expression> | A small chip at the bottom of the card. | tag self.DamageType |
wide <expression> | Renders the line full-width. | wide self.LongDescription |
flavor, tag, and wide accept any expression β not just string literals. flavor "You hit " + target.Name + "!" is valid.
Chat Card Content Types β
chat Example {
"Literal text" // Plain text
variableName // Variable value
self.PropertyName // Property value
tag self.Property // Tagged property (small chip)
flavor "Description" // Flavor text at the top
wide self.LongDescription // Full-width line
}Advanced Chat Card β
chat AttackResult {
flavor success ? "Hit!" : "Miss!" // Conditional flavor text
"Damage: " + damage // String concatenation
attackRoll // Roll object: renders with breakdown
damage // Roll object: renders with breakdown
tag self.WeaponType // Tagged weapon
tag target.Name // Tagged target
}Loops and Iteration β
Each Loop Syntax β
each <variable> in <collection> {
// loop body
}Collection Types β
each item in self.Equipment { } // Property collection
each skill in self.Skills { } // Document array
each bonus in bonusArray { } // Variable array
each level in [1 to self.Level] { } // Number rangeNumber Range Syntax β
[startNumber to endNumber] // Inclusive range
[1 to 10] // Numbers 1 through 10
[self.MinLevel to self.MaxLevel] // Dynamic rangeActions β
Action Syntax β
[modifiers] action <name>(<parameters>) {
// action body
}Action Modifiers β
Prefix an action with a modifier to control where it appears and who can see it:
macro action Name { } // Eligible for the Foundry macro hotbar
gmOnly action Name { } // Only shown to GMs; hidden from players in item table buttons
secret action Name { } // Shown to GMs and the document's owner; hidden from others
hidden action Name { } // Never shown in the UI (useful for macro-only actions)Note
gmOnly, secret, and hidden apply to action buttons rendered in item table columns. They work alongside the visibility: parameter β visibility: controls when a button is enabled or locked on actor sheets; prefix modifiers control who can see the button at all.
Action Parameters β
| Parameter | Description | Example |
|---|---|---|
visibility: | Control visibility | visibility: Visibility.gmOnly |
icon: | Action icon | icon: "fa-solid fa-sword" |
color: | Action color | color: "#FF0000" |
label: | Action label | label: "Custom Name" |
Visibility Values β
| Value | Description |
|---|---|
Visibility.unlocked | Fully visible and editable |
Visibility.default | Standard visibility |
Visibility.secret | Hidden from players, visible to GMs |
Visibility.edit | Only visible in edit mode |
Visibility.play | Only visible in play mode |
Visibility.gmEdit | GMs can edit, players view |
Visibility.gmOnly | Only visible to GMs |
Visibility.readonly | Visible but not interactive |
Visibility.locked | Locked from interaction |
Visibility.hidden | Completely hidden |
Event Handlers β
Hook Handler Syntax β
on <eventName>(<parameters>) {
// event handling code
}Combat Events β
| Event | Description | Parameters |
|---|---|---|
combatStart | Combat begins | None |
combatEnd | Combat ends | None |
turnStart | Character's turn starts | None |
turnEnd | Character's turn ends | None |
turnIsNext | Character's turn is next | None |
roundStart | Combat round starts | None |
roundEnd | Combat round ends | None |
Health Events β
ISDL fires these hooks on its own when the damage applicator runs. The pre-apply hooks let you mutate the incoming amount before it lands.
| Event | Description | Parameters |
|---|---|---|
preApplyDamage | Before damage is applied to this document | number amount, string damageType, object damageMetadata |
appliedDamage | After damage is applied | number amount, string damageType, object damageMetadata |
preApplyHealing | Before healing is applied | number amount, string damageType, object damageMetadata |
appliedHealing | After healing is applied | number amount, string damageType, object damageMetadata |
preApplyTemp | Before temp HP is applied | number amount, string damageType, object damageMetadata |
appliedTemp | After temp HP is applied | number amount, string damageType, object damageMetadata |
death | The document's health resource hit 0 | None |
Listening to Foundry Hooks β
Any identifier you put after on becomes a Hooks.on(<name>, ...) listener for a Foundry hook of that name. This means you can listen to any hook Foundry itself fires (e.g. createActor, updateItem, chatMessage, renderChatLog) or hooks fired by modules.
on createActor(actor, options, userId) {
// runs whenever a new actor is created
}ISDL does not currently provide a way to fire custom hooks from your own code. The combat, health, and death events listed above are the only hooks ISDL emits on your behalf; everything else must be fired by Foundry, a module, or external code calling Hooks.callAll(...).
Interactive Prompts β
Prompt Syntax β
fleeting result = prompt(<parameters>) {
<field definitions>
}Prompt Parameters β
| Parameter | Description | Example |
|---|---|---|
target: | Who sees prompt | target: "user" |
label: | Window title | label: "Choose Action" |
icon: | Window icon | icon: "fa-solid fa-question" |
width: | Window width | width: 400 |
height: | Window height | height: 300 |
location: | Window position | location: 100, 200 |
limit: | Time limit | limit: 30 seconds |
Prompt Target Values β
"user"- Current user"gm"- Game master"target"- Targeted player
Allowed Prompt Fields β
Prompts only accept one-shot input fields: string, number, boolean, choice<string>, choices<string>, choice<damageType>, choice<Document>, choices<Document>, parent<...>/self<...> references, die, dice, date, time, datetime. Persistent or display widgets (attribute, resource, tracker, money, html, paperdoll), embedded collections (tables/inventories), and layout blocks are not allowed and produce a validation error. See Interactivity.
Time Units β
ms- Millisecondsseconds- Secondsminutes- Minutes
Timing and Audio β
Wait Syntax β
wait <duration> <unit>Audio Playback β
play(file: "path/to/audio.wav", volume: 75)Audio Parameters β
| Parameter | Description | Default |
|---|---|---|
file: | Audio file path | Required |
volume: | Volume 0-100 | System default |
Type Checking β
Document Type Checks β
target is Actor // Check if target is actor
target is Item // Check if target is item
parent is Actor // Check parent document typeUpdate Methods β
Document Updates β
self.update() // Commit pending changes to the document now
self.delete() // Delete the current documentself.update() is the canonical form. It commits whatever assignments the action has queued so that subsequent code in the same action can read the updated values. The end of an action body implicitly flushes pending updates, so you only need self.update() when in-action read-after-write ordering matters.
Debug and Logging β
Log Function β
log(message1, message2, ...) // Output to consoleLog Examples β
log("Debug message")
log("Value:", variable)
log("Multiple", "values", 123, true)JavaScript Escape Hatch β
JavaScript Block Syntax β
@js{ JavaScript code here }JavaScript Examples β
action JSExample {
@js{ const value = Math.sqrt(25); }
fleeting result = self.BaseValue + @js{ value }
@js{ console.log("Custom JavaScript executed"); }
}Warning
@js{...} is an escape hatch, not a supported feature surface. The block executes inside whatever method ISDL is generating, with access to whatever variables the surrounding code has in scope (typically this, document, context, update, system, etc. β but the exact set depends on where in the action body you use it). There is no stable contract for what's available; if ISDL's codegen changes, your @js{...} blocks may break. Use it for genuine gaps in the language, and prefer ISDL constructs when they exist. For larger or system-wide native code (settings, extra sheets, UI hooks, module integration), use the stable, regeneration-safe Custom Code & Styles files instead.
Expression Precedence β
Operations are evaluated in this order (highest to lowest precedence):
- Parentheses -
(expression) - Negation -
!expression,-expression - Multiplication/Division -
*,/ - Addition/Subtraction -
+,- - Comparisons -
<,>,<=,>=,==,!=,equals,!equals - Logical AND -
and - Logical OR -
or
Precedence Examples β
fleeting result = 5 + 3 * 2 // = 11 (not 16)
fleeting result = (5 + 3) * 2 // = 16
fleeting result = x > 5 and y < 10 // Comparison before ANDCommon Patterns β
Null-Safe Operations β
if (self.OptionalProperty exists and self.OptionalProperty > 0) {
// Safe to use property
}Array Bounds Checking β
if (index >= 0 and index < array.length) {
fleeting value = array[index]
}Safe Division β
fleeting result = divisor != 0 ? dividend / divisor : 0Complex Conditionals β
if ((self.Level >= 5 and self.Class equals "Mage") or
(self.Level >= 8 and self.HasSpecialTraining)) {
// Complex condition logic
}This reference covers all ISDL logic syntax. For practical examples, see Basic Logic, Advanced Logic, and Interactivity.