Skip to main content

Code frameworks

How Fabled runs your code

Follow a project from source to play: what runs when, how the ledger works, and what a scenario proves.

From source to play

Your project moves through five steps. Only the last one is play. The steps before it never change a live character.

  1. Write

    You edit the source files in the source editor. Nothing runs yet.

  2. Check

    Check and preview compiles your current edits and runs every scenario, saved or not. Problems appear in the Problems panel. Nothing is published.

  3. Save

    Save project keeps the source as a new version of your game. Prepare only works on a saved version, so save before you prepare.

  4. Prepare

    Prepare my game builds private Frameworks, Frames and content from the saved version. The server checks the project again, and its verdict is final.

  5. Play

    A player creates a character from your Framework. The constructor builds it, and actions change it.

What runs when

PartWhen it runsWhat it does
ConstructorOnce, when an Element is created from the Framework.Turns the inputs into the starting values. It never writes.
Ledger functionWhen the Element is built, in the order the schema declares the functions, and again after each action.Derives a value from stored values. An action cannot write it.
ActionWhen a player uses a button that calls it.Writes to the ledger with ledger.set and ledger.add.
FrameWhenever the sheet is shown.Reads stored values and draws them. Its buttons call actions.
ScenarioIn Check and preview, never in play.Builds an Element, runs actions and compares every value and write.

The ledger

An Element's values are never edited in place. Every change is a write in its ledger. ledger.set replaces a value and ledger.add adds to it. Scenarios list those writes, so you see exactly what a rule does.

A derived value comes from a ledger function, so an action may not write it. The check refuses it:

which is a non-writable state field

The constructor still returns a field that a ledger function derives, with a placeholder such as 0. The ledger function replaces the placeholder when the Element is built. A scenario compares its expectedValues with the values after the ledger functions ran, so write the derived value there, not the placeholder.

Ledger functions run in the order the schema declares them, and each sees the results of the earlier ones. Declare a function after the functions whose fields it reads. Here total reads base, but it is declared first, so it reads the placeholder:

{
  "ledgerFunctions": [
    {
      "id": "total",
      "name": "Total",
      "field": "total",
      "source": "rules/total.ts"
    },
    {
      "id": "base",
      "name": "Base",
      "field": "base",
      "source": "rules/base.ts"
    }
  ]
}
expectedValues.total: expected 11, constructor returned 1

Use a ledger function for a value that must follow other fields and that no action writes. Use a constructor value for a number that the character owns and actions change, such as hp. The constructor computes it once, so a later change to the fields it came from does not move it.

What a scenario proves

  • The constructor returns the values you expect.
  • Each action makes exactly the writes you list.
  • Derived values come out right, also after an action.
  • Every file is used and the whole project compiles.

A scenario does not prove how a sheet looks, or how a rule feels in play. Try the prepared game for that.

Next