Actor Notes

See also: The Authoring Workflow, Item Note Frontmatter, Asset Conventions

An actor note is a markdown note that compiles to a Foundry Actor. Like an item note it carries an essence rather than a mirror of the Foundry document: the compiler supplies the envelope, and the note says what this creature is, what it can do, and what it is carrying.

The distinguishing feature, and the thing most worth understanding before you write one, is that an actor note does not contain its items — it addresses them. Its skills, attributes and gear are named by (type, shortcode) and resolved at build time against the compiled item packs. That address space is what the second half of this page is about.

One type compiles, not four

The system defines four Actor types — being, cohort, structure and vehicle (see the Type Catalog). The content pipeline compiles exactly one of them: being. All 95 actor notes in assets/content/ are beings.

That is not an oversight in the compiler so much as a statement about where those documents come from. A cohort, a structure and a vehicle are created in play, from a sheet; none of them is a thing an author writes down in advance and ships in a compendium. Only a being is.

The consequence for an author is worth stating plainly: a note whose type: is cohort, structure or vehicle is claimed by no compiler pass and is skipped without a word. It is not an error — the walk simply finds no pass that selects it — so it compiles nothing, reports nothing, and looks exactly like a note that worked. The same silence covers any misspelled type.

Two spellings do fail loudly. character and creature were one distinction with two spellings that compiled to the same being with no branch anywhere between them; they were retired in #1580 and now throw, naming the replacement and saying the fix is mechanical.

The envelope

A being carries the frontmatter every note carries — name.full, type, shortcode, id, folder, and the required sohl.archetype — all described in The Authoring Workflow. Two art fields are specific to actors:

FieldBecomesDefault
imgthe actor’s image, and its prototype token’s artthe generic person icon
portraitsystem.portrait, the sheet’s character portraitthe same generic person icon

Both resolve through the rules in Asset Conventions.

The body prose is two documents, not one. A being’s markdown body is read for two anchored sections, and only those:

# Appearance {#appearance}

Weathered, and taller than most.

# Dossier {#dossier}

Known to the watch at Kaldor.

They become system.appearance and system.dossier. Prose outside a recognised anchor is not compiled anywhere — unlike an item note, whose whole body becomes its documentation journal.

The sohl: block

FieldShapeMeaning
attributesmap of shortcode → numberEmbedded attribute items, each opened at that score.
itemslist of entriesEverything else the being embeds. See below.
bodynested objectThe being’s physical body. Omit it for an incorporeal being.
currentMoveMediumstring, default "none"Which medium it is currently moving through.
movementProfileslist of profilesSpeed per medium.
defaultCombatGroupstringWhich side it joins in a combat. Emitted only when declared.
archetypenumber or nullRequired, as on every item note.
folderfolder idFrom actor-folders.yaml.

sohl.skills is ignored. It is read by nothing and compiles to nothing. Skills are embedded through sohl.items, like every other item; a skills: block is silently inert, which makes it exactly the kind of authoring mistake this page exists to prevent.

body

sohl.body mirrors system.body field for field: structure, weight (base, calc), reachBase, bodyScaleBase and personalFatigue. Movement sits flat alongside it rather than inside it, because movement is a universal actor capability rather than a property of a body.

Absence is meaningful. A being that omits sohl.body is incorporeal and keeps the schema’s empty body — that is a supported state, not a missing field. What is not supported is a sohl.body that is present but not an object; that is reported as an error naming the note and the type it got instead.

sohl:
  body:
    weight:
      base: 165
    reachBase: 5
    bodyScaleBase: 1
    personalFatigue: enc
  currentMoveMedium: terrestrial
  movementProfiles:
    - medium: terrestrial
      feetPerRound: 30
      leaguesPerWatch: 4

The address space

This is the part that has no counterpart in an item note.

A being names an item by (type, shortcode), and never by the pack it ships in. At build time the actors pass loads the generated JSON of every Item pack into one map keyed type:shortcode, and each embedded entry is looked up in it.

sohl:
  attributes:
    str: 13
    agl: 11
  items:
    - type: skill
      shortcode: Dagger
    - type: weapongear
      shortcode: Dagger
      system:
        qualityBase: 2

An entry is type (required), an optional shortcode, and any other keys, which are deep-merged over the resolved item: plain objects merge key by key, while arrays, primitives and null replace outright. That is how the weapongear above ships as a better-than-average dagger without restating the dagger.

Attributes are the same mechanism with a shorthand — str: 13 resolves attribute:str and overlays system.scoreBase.

When an address does not resolve

A dead address is a hard error, not a dropped item:

actor "Trader Wulfe": no predefined item for "weapongear:Dagr"

The pass records it, keeps going, and the accumulated error count makes the build refuse to compile the packs. So a mistyped shortcode fails the build rather than shipping an actor quietly missing a weapon — the opposite of the skills: behaviour above, and the reason to prefer an address over an inline item wherever one exists.

Other errors in the same family: an entry that is not an object, an entry with no type, and an entry with neither a shortcode nor enough fields to stand alone (an item with no address must supply at least a name and a system).

Uniqueness spans every Item pack

Since #1566 a repository may group its items into several Item packs. Because a being addresses an item by (type, shortcode) alone, those packs are read as one address space, and two packs claiming the same address is ambiguous rather than a last-one-wins ordering detail:

Two Item packs both define "weapongear:Dagger" (…/items and …/relics); a being
addresses an item by (type, shortcode), so the address must be unique across
every Item pack

Items are compiled first, and that is load-bearing

The actors pass reads the items passes’ output, so it must run after them — content-build package compile orders them for you. Compiling the actors pack alone against a missing or stale items tree fails outright:

Items source directory … does not exist — actors must be generated after items

A cross-package address does not work

The address space is one build’s Item packs — not the packages it depends on. A being in a module cannot embed an item defined in another package’s compendium by shortcode, because that package’s items were never compiled into this build’s JSON tree and are simply absent from the map.

This is not hypothetical. It is what blocked the sohl-kethira-basic migration (#1513): its 17 character notes carry 895 embedded-item references across 113 distinct addresses belonging to the sohl package, which that repository does not hold. Every one of them resolves to nothing.

There are two honest answers, and no third. Either the module defines the items it embeds, in its own content tree and its own Item pack, or the being carries the item inline — an entry with no shortcode, supplying its own name and system, which stands alone precisely because it addresses nothing. Cross-package links are a solved problem via the link manifest, but a link is a reference a reader follows; an embedded item is data that has to exist at build time.