v0.54.0 — 5e action definitions
Picks up v0.51.0's first draft (vtt-modules, unpublished dnd5e 0.4.0). The spec, the closed
vocabularies and validateSpec are written and tested; what has never happened is a real run.
- [x] AA-1 — Run the sample and read the drafts.
authoring:spell-actions --slugs=fireball,fire-bolt,cure-wounds,fly. The prompt is a hypothesis until this happens, and the drafts are what answer the two questions the spec left open: whether the caster's slot choice belongs in the spec or in the action's own prompt (3c.4 and 5E-3c both wait on this), and whetherconfidence: lowspecs are worth keeping. NeedsANTHROPIC_API_KEY. Done: cantrips and all 48 level-1 spells authored. Both questions answered by the run — the slot stays out of the spec (it is chosen when the button is pressed, and derivation happens when the sheet is saved), andconfidence: lowis dropped at promotion by default. - [x] AA-2 — The spec → action script translator. Deliberately ordinary code, not a second model
call: a spec is a closed vocabulary precisely so the translation is total and testable.
Done:
core/specToActions.ts, plusscripts/promote-spell-actions.mjsfor the review gate between the draft and the shipped data. - [ ] AA-3 — Tune the prompt against a wider sample — a stratified ~30 spells across attack, save, heal, utility and the awkward middle, scored against hand-written expectations.
- [ ] AA-4 — The full run over all 317 spells, with the refusal rate and the
unmodelledlists reported, not just the successes. - [x] AA-5 — Where the output lives and how the module serves it. Then wire it in: a character's
spells get their authored actions the way 5E-3b's derived ones arrive today.
Done:
data/<edition>/spell-actions.json, read bybuildDeriveSpellActions, which prefers an authored spec and falls back to the regex parser for a spell nobody has authored. An authored spell with NO actions is an answer, not an absence, and outranks the parser.
Spells that paint the map
The gap the first real run exposed. Roughly a seventh of level 1 — Faerie Fire, Grease, Entangle,
Web — is an area spell with no damage in it, and the translator refuses every one, because a save
that only imposes a condition is a save the tabletop cannot resolve. That refusal is correct and
should stay. But it also throws away the half of those spells the tabletop can do: Faerie Fire's
20-foot cube is a shape on the map, and drawing it is worth having long before anything can apply
charmed to what stands inside it.
- [x] AA-6 —
draw()can draw where the user aimed. The drawing half is already built —makeDrawApi(apps/web/src/utils/drawAction.ts) puts adrawin the action sandbox besiderollandtarget, with circle / square / rectangle / cone / line / wall, adurationin rounds that the initiative tracker cleans up, and the samecreateDrawingpath the toolbar uses. Three things stand between it and this feature: -originis pixels relative to the executing token, so an action can draw around itself and nowhere else. -useTargetingMode'ssetOnAreaConfirmed(placedArea, hitTokens)dropsplacedArea— the one thing that knows where the reticle landed. -_targetingMetadatais written into the execution context and never read byactionExecutor, so even today's area metadata does not reach action code. The fix is a passthrough and a squares→pixels conversion, not a new drawing system. Also exposeIDrawing.textthroughDrawArgs: the drawing model already carries a visible label, andmetadata(a hover tooltip) is not one. - [x] AA-7 — Teach the authoring pass which area effects PERSIST. A new spec field, and the distinction it has to draw is the hard part: - Paints the world — Wall of Fire, Sleet Storm, Web, Grease, Cloud of Daggers. The area itself endures and keeps mattering to whatever enters it. This wants a drawing. - Catches what is standing there, then stops being a place — an instantaneous burst whose rider lingers on the creatures it caught. Many System 8 powers are this shape: ongoing damage or a debuff carried by the target, with no persistent zone. This wants no drawing; one would leave a permanent scorch mark where a fireball briefly was. The failure mode is asymmetric, which decides the default: a missing drawing is a spell the GM narrates as usual, while a spurious one is map litter every player can see and nobody can explain. So the model refuses when unsure, as it does everywhere else in this pipeline. Note that concentration and a stated duration are hints, not the rule — Faerie Fire persists and is a zone; Hold Person persists and is not.
- [x] AA-8 — The action shape itself. Targeting mode exactly as a damaging spell uses it, and on
confirm the action creates a drawing in the chosen area, labelled with the effect
(
Faerie Fire — outlined, advantage to attackers). Where it also has a save or damage, one action does both: the drawing is what the area leaves behind, not a second button.specActionRefusalthen stops declining a save with no damage when the spec says the area persists — that action now resolves something, which is the only reason it was ever refused.
Map click no-reg
- [x] When left-clicking the map, and the cursor is not over an interactive element (i.e. a token on the current layer), currently nothing happens. In this change, when this click occurs, but another token or drawing on some other layer is present, the click pops up a context menu showing all possible tokens/drawings on other layers matching the cursor position. Selecting one of these selects the given token/drawing, changing the current layer accordingly.