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_rootis the single immutable enumeration. Every campaign-wide effect walk in the workspace is defined in terms of it or offor_each_campaign_effect, which is defined in terms of it.for_each_effect_root_mutis 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.EffectRootKindnames the roots.EffectRootKind::ALLis 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_unlockwas already a root and nobody had noticed. It is aVec<QuestEffect>hanging off a stage-5 struct, structurally identical in kind totraps[].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: anarrateinside it was never inventoried, aset-flaginside it was invisible to the flag model, and asequenceinside it would have emitted afunctioncall 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_deathis 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§
- Effect
Root Site - One effect root: which it is, where it is, and what its l10n keys hang off.
- Root
Binding - What a walk over the effect roots actually examined.
Enums§
- Effect
Root Kind - Which of the campaign’s effect roots a bundle is.
- Effect
Root Owner - What a root hangs off, with the owning object attached.
- Effect
Site - 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§
- Root
Visitor Mut - The callback
for_each_effect_root_muthands each root to:(kind, json_pointer_to_the_list, l10n_keybase, list).'aties the effects to the campaign borrow, so a consumer may collect them;'fis the callback’s own borrow.