Ledger v3 · C# · Godot 4.7 Get started
The author's guide

Traps: what the language can't say yet

Each trap here is a debt of the language, not a rule of it: something an author meets that content can't say today, what it says instead, and what the game does meanwhile. Each gives its grounds: the refusal's words, with its id where ledger check --json gives one, or what a play shows. When a form closes a trap, the trap leaves this page and changes.md says what closed it; until then, write the way round it gives, and say in your notes that you met it.

Each trap names its witness, a play in witnesses with the catalog it plays, which holds the trap as it stands: refused at load, by the id or the words given; a play failing in the words given; or a play that passes, expecting the surprising result. Run one with ledger play guide\ledger-author\witnesses\NAME.play. The kernel's tests run every witness, so a trap that closes fails its witness until it leaves this page. A trap with nothing a play can hold, an absence with no spelling to refuse, says so in a line.

Refused at load

Clauses out of the spelling's order. A statement's clauses go in the order ledger forms spells them. One out of order is refused at the word, saying where it goes: apply Dosed to up to 1 others ... with dose = self.dose from source is refused "unexpected 'from': it goes before 'with'" (L0106), and the form's spelling follows. Move the clause; ledger check --json gives the spelling as form. Witness: witnesses/clause-order.play, refused L0106.

An effect that becomes another at its end. then EFFECT is no line of an effect's ("an effect can't say 'then'", no id yet). Apply the next effect in the first one's expire, or apply both at once, the second waiting out the first. Witness: witnesses/effect-then.play, refused "an effect can't say 'then'".

An aura whose reach reads a value. aura Chilled on all enemies within holder.Reach of holder follows its holder's Reach: an aura is worked out again as tags and places change, and as what its reach reads moves, which is its holder's own values, another actor's through a link of its holder's, a global link or its source, and a count of a query finding actors by side, tags and presence. A reach reading more, such as a value of the actors a query finds, a count by place, a pool that drifts or its effect's time, changes without telling it, so "one reading more than its holder's values, another's through a link and counts by side, tags and presence is sampled, every D" (no id yet): sample that one with every D. Witness: witnesses/aura-reach.play, which passes.

Two rules hearing one event, with nothing to order them. Two effects whose rules hear the same event at the same stage, where their order changes the result, are refused: "A and B both hear Damage by holder before it lands, at no stage, and nothing says which goes first ... give both an order key" (no id yet). The fix is in both files: a new effect's rule can make an old effect's file change, to give it a stage or an order key. Witness: witnesses/two-rules.play, refused "give both an order key".

A play's own rows. A play file's row lines are the kernel's census's, by its ids: row T01 Hit Creep is refused "an id is a letter A, B or G and three digits" (no id yet). A game's plays name no rows; every expected number says where it comes from with from content: and its arithmetic in words. Witness: witnesses/play-rows.play, refused "an id is a letter A, B or G and three digits".

A template of definitions, retuned by a mod. A template with no kind is written out as its own file is read, before any package's extend or replace meets it, so a later file's replace template NAME is refused "no template named NAME" (no id yet), where a typed template's replacement reaches every use. A mod changes what the template wrote: extend or replace each definition by its name, as if written out. Witness: witnesses/definitions-replaced.play, refused "no template named Rolled".

A template of definitions, formatted. ledger format reads a file a line at a time and refuses one holding a template of definitions at its {NAME} ("unexpected character '{'"), though ledger check loads it. Lay such a file out by hand, two spaces a level as format would. No witness: a play loads the catalog, which holds, and only the formatter refuses it.

Loads, and does something else

strongest on a slow. Answered: of a category's terms on a stat, the one that moves it furthest holds, an add's or a percent's furthest from 0 and a factor's from 1 by ratio, so MoveSpeed x 1 - source.Chill strongest Slows keeps the strongest cold, and a slow needs no stat of its own. A category's terms are of one kind: a factor and an add in one category are refused at load, "Slows holds a factor (line 9), and this term is an add: a category's terms are weighed against each other, so they are of one kind ...". What is left: no word keeps the largest term instead. Witness: witnesses/strongest.play, which passes.

A lifetime counted in events. Answered: a lifetime ends expired however it is counted, so an effect that lasts 2 RoundStarted ends End.Expired at the second, its expire running before its end rules, as for time: a cloud meant to vanish does. End.Spent is what is used up, an each ... removes taking the last stacks or signed stacks summing to 0. An end rule written before, end having End.Spent on a count, loads and no longer runs; ledger check warns, "ByEnd's end rule names End.Spent, which nothing gives ByEnd: ... write end having End.Expired, or expire". Witness: witnesses/ends-spent.play, which passes.

A prevent after an absorb. A hit a prevent stops still drains a shield whose absorb runs at a stage, since the absorb ran first (a play expecting the shield at 50 finds it at 20). Order the two by stage: stages Ward Soak, the prevent at Ward, the absorb at Soak. Witness: witnesses/prevent-absorb.play, which passes.

The top of a pile, in its own item's rule. While an item's expire runs, the item is still its pile's top, whether or not its rule has destroyed it, so "the next" taken from the top is the expiring item and the queue sticks (a play expecting the next training finds none). Take the next except holder. Witness: witnesses/pile-top.play, which passes.

A template's tags, replaced. A tags line under from TEMPLATE(...) replaces the template's tags, so an ability that says tags Gun no longer carries the template's Attack, and a block of Attack misses it. To keep the template's tag, write it again beside the new one: tags Attack Gun. Witness: witnesses/template-tags.play, which passes.

having in an event filter asks the event. after Died of anyone having Hero reads the Died event's tags, not its subject's, so with a Died raised with no tag the rule never runs, whoever died. To ask the subject, write after Died of anyone when has(event.subject, Hero). Where the event is the content's own (event Died), the loader can tell: it warns where no raise of that event writes the tag (tagged Hero) and the tag isn't carried, "... asks the event for Hero, which no raise of Died tags it with, so it never runs". An ability's events (an Activate, a Commit, a Pulse, a Finished, a Contact, a Stopped, a Resolved) carry its tags, and an Apply and a Remove their effect's, so for those it warns where no ability, or no effect, carries every tag the list names (the next trap). A Change carries its steps' tags (deal 20 Fire Infernal to target's Infernal), its portions' types, the tags declared carried down its chain, the kernel's marks, for a cost its ability's own, and for a deal the host gives (World.Deal, a play's deal) any tag it names; an ability's other tags are its activation's alone. So after Damage of holder having Hit, where Hit is an ability's tag no deal writes, runs only where the host deals tagged Hit, and the loader warns: "... asks the change for Hit, which no step deals and no chain carries, so only a host's deal tagged Hit runs it: an ability's tags are its activation's and its costs', and its other changes carry one only if it is declared carried (tags Hit carried)"; a rule on a change no deal makes (a gain, a heal, a cost) "never runs". Where a step deals a tag a property holds, the loader can't list it and says nothing: check the tag is the change's, as ledger play --journal LINE shows each row's. Witness: witnesses/having-event.play, which passes.

having asks for every tag it names. after Activate by holder having Special Super hears a move carrying both, not either, so where no move carries both it loads and never runs. unless reads the other way: blocks Activate unless Special Super Tech lets through a move carrying any of them. For either of two tags, write a rule for each (having Special, then having Super), or give the two a tag they share and ask for that. Where the event's tags are an ability's or an effect's, ledger check warns: "... asks the event for Special and Super, and no ability carries them all, so it never runs: having asks for every tag it names and unless refuses any; to ask for either, write a rule for each tag, or give them a tag they share and ask for that". Witness: witnesses/having-all.play, which passes: the trap's rule never runs, and both fixes' do.

An item's gift has the item as its source. An effect an item gives its keeper (gives Burning) has the item as its source, and so does what the gift's aura puts on others. So deal source.Might to holder reads the charm's Might, not its keeper's, and an item with no Might reads 0: a weapon given by a card can deal nothing in every run. A rule that reads its giver's stats reads the taker: holder.Might in the gift itself, and credit.Might in what its aura puts on others, which is credited to the keeper. A play that applies the effect straight to an actor (apply Burning to h) tests another source than the game gives, and passes. Give it as the game does: make c Charm and move c to h.Kit. ledger check warns where an effect only items give reads source.STAT and an item giving it has no such stat, "... and an item's gift has the item as its source"; where the item has one, as here, nothing can warn. Witness: witnesses/gift-source.play, which passes.

A read through no grant gives the stat's default. event.grant.source is the item an activation came through, a skill's gem; a change no grant made (a burn's tick, an effect's every) has none. A stat read through no actor, or through one gone, gives its declared default, as a read through an actor without the stat does, and a pool gives 0. So scale event.grant.source.More.Fire in a backbone's rule, More declaring default 1, lands a burn's tick whole. But a guard by a stat's value no longer tells a grant from none: a stat that declares a default above 0 reads it through no grant, so if(event.grant.source.Area > 0, ...) takes its first branch either way. To tell them apart, guard by a stat that declares no default, which reads 0 through no grant, and that every gem holds above 0. Witness: witnesses/no-grant-read.play, which passes: the bare read lands the tick whole, the guard by Area passes through no grant, and the guard by Level, which declares no default, tells it.

A made unit's hits are its own grant's. A totem a skill makes (create Totem ... at target) casts its bolt through its archetype's own grant, so the bolt's event.grant.source is the totem's, not the gem that made it, and its hits are credited to the totem. To read the gem, set a link as the unit is made (create Totem with gem = source) and read through it (holder.gem.More.Fire); a batch's why lists the bolt's damage under the totem's archetype, beside the hero's. No witness: the trap shows only where a unit made in a game's space chooses its own targets by its auto, among sides; the ARPG's FlameTotem and TotemBolt are written so, and its field's why lists the bolts under the totem.

A request in an effect is its holder's. request COMMAND asks on behalf of the actor whose rule it is, and in an effect that is the effect's holder. The plane's Push drives its target straight away from the requester, so request Push holder by 1 in the effect on a foe pushes the foe away from itself and moves nothing. To push away from the giver, request it from the giver's side: a rule of an effect the giver holds, heard on the hurt it deals (after Damage by holder, then request Push event.subject by 1). Witness: witnesses/request-holder.play, which passes.

by source on a Stopped hears only its caster's stop. A Stopped is credited to whoever stopped the activation, so a tie that ends on Stopped by source stands when another actor stops the caster's channel. ends on Stopped of source hears the caster's activation end however it goes. Witness: witnesses/stopped-by.play, which passes.

A tie goes to the first-made actor. Two moments at one tick, two strikes, two swings or two casts, go in the order their actors were made, the first-made first; no draw breaks the tie. So a mirror leans to the side made first: two boards alike, each side's bodies made in the same order every fight, win far from even. The way round is content's: draw which side is made, or acts, first (for each 2 random actors having Player using Muster as pl, then each side musters in that order), and batch a mirror to see it even. Witness: witnesses/tie-first-made.play, which passes: each order played, and the fighter made first wins each.

lasts 1t from an order between ticks. An effect an order applies between ticks, as a game's BeforeTick gives one, counts its time from the tick that ran last, where the world's clock stands, as everything given there does. So effect Jumped lasts 1t, applied by the jump the game ordered, ends as the next tick runs, and the game's read after that tick never sees it; the same effect applied by a rule inside a tick counts from that tick, and is read once. One lasts 1t holds one host read or none, by where it was applied. Give a one-tick impulse the game turns into a body's speed no lasts, and take it off as it is read: where W.Has(hero, "Jumped"), S.Give(o.Remove(hero, "Jumped")). Witness: witnesses/impulse.play, which passes: the order's impulse is gone at the next tick, the same one applied as a windup ends is held through that tick, and one with no lasts stays until taken off.

Content can't say it; the game does

Making at a place. create Cloud at target stands the made actor at the cast's point, and create l at holder under a death rule lays a drop where the foe fell; the game's space stands it as its Create is raised, and a space that can't is told as a fault. What is still the game's: an effect's var can't hold a point, so a place kept for later (a storm's next spot, a beacon's mark) is an actor the game stands there, or the game's own. Witness: witnesses/make-place.play, which passes.

A body's size. The plane measures centre to centre, so target one enemy within 1 never reaches a base of radius 2 that no body may stand inside. A space of the game's own measures edge to edge (host.md). Witness: witnesses/centre-reach.play, which passes.

Standing orders. An order is one activation: nothing in the world lasts as move, attack, attack-move, hold or gather do. The game keeps each unit's order, and each tick asks the world who is in reach and then gives the ability. In a real-time strategy game this is most of the C# a game writes beside its content. Witness: witnesses/one-order.play, which passes.

Areas of one kind that don't stack. Nothing says "once a beat for every area of this kind": two storms overlapping both strike. Mark what a beat struck with a short effect lasting a little less than the beat, and skip the marked; the mark's length is tuned to the beat by hand. Witness: witnesses/areas-stack.play, which passes.

One ability, many flights. An ability that flies looses one projectile. A fan of six is six orders, each with its own direction. No witness: nothing spells a fan, so there is no word to refuse, and a play of one flight shows only what works.

A pierce that remembers whom. Answered: a flight keeps the actors it has flown on past, and a report naming one again is refused AlreadyMet, nothing run, the flight flying on. So report every foe a lance overlaps and keep no list of your own; its save keeps them, and W.Met(flight, list) gives them, for an engine's collision exceptions set again after a quickload (host.md). What is left: a flight that should meet one actor twice, a boomerang's way back, is a second flight. Witness: witnesses/pierce-twice.play, which passes.

Sight. The world keeps no sight. A game whose sides don't see everything keeps its own, and its space answers the content's fact (requires fact(Seen, holder, target) > 0), so the world refuses a target in fog (visibility.md). No witness: the world keeps no sight and nothing spells one, so there is nothing to refuse or to play.

A play can't tell

nearest within a reach. up to 1 ... within 1.5 of holder already takes the nearest, so no play can tell nearest's work from its absence. Write it if you mean it; a play won't show it. Witness: witnesses/nearest.play, which passes.

A made actor, by more than its kind. find NAME ARCHETYPE names a made actor by its kind alone, the newest of it, so a play choosing one of a pick's three is written for a kind it knows was offered, at a seed that offers it. Witness: witnesses/find-kind.play, which passes.

From ledger-v3's guide/ledger-author/traps.md, last changed 2026-11-02.