Skip to content

Logic Reference

Cody Swendrowski edited this page Jun 6, 2026 · 13 revisions

Logic Reference

Complete syntax reference for ISDL logic constructs, functions, and expressions.

Variables

Variable Declaration

fleeting <name> = <value>  // Mutable variable
eternal <name> = <value>   // Immutable constant

Variable 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 index

Mathematical 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 parameter

Return Types

  • number - Numeric value
  • boolean - True/false value
  • string - Text value
  • nothing - 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 access

Parent Properties

Requires a type guard. parent.* only resolves inside an if (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 access

Special 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 effects

System Properties

User Properties

User.isGM                     // Boolean: Is user a GM?
User.name                     // String: User's name

Combat 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 combat

Dice 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 count

Detection 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 = false

The 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 as roll(...)).
  • type: - The damage type. Typically a choice<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 range

Number Range Syntax

[startNumber to endNumber]            // Inclusive range
[1 to 10]                            // Numbers 1 through 10
[self.MinLevel to self.MaxLevel]     // Dynamic range

Actions

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 - Milliseconds
  • seconds - Seconds
  • minutes - 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 type

Update Methods

Document Updates

self.update()                // Commit pending changes to the document now
self.delete()                // Delete the current document

self.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 console

Log 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):

  1. Parentheses - (expression)
  2. Negation - !expression, -expression
  3. Multiplication/Division - *, /
  4. Addition/Subtraction - +, -
  5. Comparisons - <, >, <=, >=, ==, !=, equals, !equals
  6. Logical AND - and
  7. 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 AND

Common 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 : 0

Complex 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.

Clone this wiki locally