Skip to main content

Module effects

Module effects 

Source
Expand description

The one enumeration of the campaign’s effect roots.

An effect root is a Vec<QuestEffect> that emission can lower. There are seven, they hang off two different stage documents, and nothing about the shape of the DSL makes them findable by inspection — which is why every walk that needed “every effect” was, historically, written by someone enumerating the roots they happened to know about.

Six separate investigations each found one such walk and fixed it in place (plan::collect_region_events, l10n::each_string, timeline::walk_campaign / nav::all_effects, flow::read_flags, emit::check_effect_anchors, emit::declared_flags). A sweep after the sixth found thirteen more walkers still enumerating three or four of the five. None of them was red: a walk that visits four of five roots produces correct-looking output over any campaign that happens not to use the fifth, and it stays green until a campaign uses it.

Fixing thirteen walkers by hand fixes thirteen walkers. This module exists so that the next root — and there will be one; the fifth was added once already — is added in one place and every consumer inherits it:

  • for_each_effect_root is the single immutable enumeration. Every campaign-wide effect walk in the workspace is defined in terms of it or of for_each_campaign_effect, which is defined in terms of it.
  • for_each_effect_root_mut is its mutable mirror, generated from the same macro body ([effect_root_walk]) rather than written out a second time, so the two cannot drift: the root list exists once as tokens.
  • EffectRootKind names the roots. EffectRootKind::ALL is the closed set; the walk asserts it visited every member on every call (RootBinding), so a root that stops being enumerated is a panic in every build rather than a quietly narrower answer.
  • Consumers that need to know which root a bundle is match on EffectRootOwner. Adding a variant there is a rustc error at every such site, so a new root cannot be silently mis-classified either.

Roots 6 and 7 were added by spec-0031, and they are worth reading as a pair because they are the two ways this defect class recurs after the enumeration exists:

  • R6 shortcuts[].on_unlock was already a root and nobody had noticed. It is a Vec<QuestEffect> hanging off a stage-5 struct, structurally identical in kind to traps[].payload (which is R4), and emission really lowers it (emit::emit_shortcut_functions). It was simply never listed — so every proof, every l10n pass and every diagnostic written for “the general path” silently did not cover it: a narrate inside it was never inventoried, a set-flag inside it was invisible to the flag model, and a sequence inside it would have emitted a function call to a function nothing generated. Zero campaigns happened to use it, which is the only reason it never shipped as a bug. The sixth blind spot in the family that three earlier hand-rolled walks each closed one instance of.
  • R7 on_death is new surface that starts inside the enumeration. The whole point of adding it as a root, rather than as a hook on the checkpoint machinery that detects death, is that “the purse is dropped on death” then stops being an engine feature and becomes ordinary content in a general mechanism.

What this module deliberately does not try to be is a guard against a fourteenth hand-rolled walk being written tomorrow. Nothing in the type system can stop someone iterating campaign.quests.content.quests directly; that half of the obligation is tools/ci/check-effect-roots.py, which fails CI when a source file reaches for two or more root fields outside this module.

Determinism (ADR-0006): iteration is over BTreeMap keys and slices, in a fixed order that is part of this module’s contract — see for_each_effect_root.

Structs§

EffectRootSite
One effect root: which it is, where it is, and what its l10n keys hang off.
RootBinding
What a walk over the effect roots actually examined.

Enums§

EffectRootKind
Which of the campaign’s effect roots a bundle is.
EffectRootOwner
What a root hangs off, with the owning object attached.
EffectSite
Where in the campaign one effect sits — the attribution every per-branch proof and the branch chronicle need (spec-0025).

Functions§

for_each_campaign_effect
Visit every effect the compiler can lower — at every one of the five effect roots, top-level and transitively nested — in a fixed deterministic order, invoking f(json_pointer, site, effect).
for_each_effect_root
Visit every effect root the compiler can lower, in one fixed deterministic order, as f(&site, list).
for_each_effect_root_mut
The mutable mirror of for_each_effect_root: the identical roots, in the identical order, with the same (stage, path, key) descriptors, exposed mutably so the localization pass can rewrite player-visible strings in place.

Type Aliases§

RootVisitorMut
The callback for_each_effect_root_mut hands each root to: (kind, json_pointer_to_the_list, l10n_keybase, list). 'a ties the effects to the campaign borrow, so a consumer may collect them; 'f is the callback’s own borrow.