pub enum Op {
Show 18 variants
DealMoveDamage,
DamageFraction {
num: u32,
den: u32,
of: FractionOf,
target: Selector,
unless: Option<Predicate>,
},
HealFraction {
num: u32,
den: u32,
of: FractionOf,
target: Selector,
unless: Option<Predicate>,
},
InflictStatus {
status: String,
target: Selector,
amount: AmountSpec,
},
InflictVolatile {
kind: String,
target: Selector,
amount: AmountSpec,
},
Boost {
stat: String,
stages: i8,
target: Selector,
},
ScaleRelay {
num: u32,
den: u32,
when: Vec<Predicate>,
},
SetRelay(i64),
AddRelay(i64),
ClampRelay {
lo: i64,
hi: i64,
},
VetoIf {
cond: Predicate,
silent: bool,
},
ApplyTypeChart,
PayResource {
resource: String,
amount: u16,
target: Selector,
},
SetHp {
target: Selector,
value: u16,
when: Vec<Predicate>,
},
SetDamage {
value: DamageValue,
of: Selector,
},
DamageCurrentHpFraction {
num: u32,
den: u32,
target: Selector,
},
RepeatHits {
count: HitCount,
target: Selector,
final_hit: FinalHitRider,
},
RemoveStatus {
target: Selector,
},
}Expand description
The closed primitive op vocabulary (doc 11 §1.1 + doc 12 §3.1). Each variant
maps 1:1 to an existing ctx/RelayVar op. This closed set is the entire
expressiveness budget (doc 11 §5).
Variants§
DealMoveDamage
Writes ctx.mv.damage from the provider formula. The provider isn’t in
BattleCtx, so (exactly like minimon’s move_damage_hook) the number is
precomputed by the driver into ctx.mv.damage before the fold; this op is
the ModifyDamage subscription marker and resolves Unchanged.
DamageFraction
battler_mut(t).take_damage(of * num/den). unless skips when the
predicate holds (the Sandstorm “non-Rock chip” case).
Fields
of: FractionOfWhat the fraction is of.
HealFraction
battler_mut(t).heal(of * num/den).
Fields
of: FractionOfWhat the fraction is of.
InflictStatus
Set the selector’s non-volatile status (the on-hit secondary). For Phase 1
this directly sets BattlerState.status via the game binding; the nested
TrySetStatus veto cascade is driver orchestration (doc 11 §3, Phase 2).
Fields
amount: AmountSpecOptional numeric parameter handed to the binding (e.g. sleep turns).
Defaults to Const(0), so a plain InflictStatus is unchanged and
the binding’s set_status_with_amount default ignores it.
InflictVolatile
Install a game-defined live volatile on the selector — the generic
counterpart to [InflictStatus] for effects that live in the effect
arena rather than the non-volatile status slot (Gen-1 confusion, Leech
Seed, Toxic-counter, flinch). The engine resolves amount (one
ctx.rng byte for the roll variants), then asks the game’s binding to
build the OPAQUE P::EffectStateKind for kind + amount; if the
binding returns one, the engine installs it generically (fresh id, kept
sorted). The engine never learns what the volatile means — only the game
(via make_volatile) does. A game with no volatiles returns None and
the op is inert.
Fields
kind: StringThe volatile’s game-vocabulary name (e.g. "confusion"), passed to
the binding — NOT interned (like Predicate::HasVolatile).
amount: AmountSpecThe numeric parameter (turns / counter seed) handed to the binding.
Boost
Apply a stat-stage delta to the selector (the Intimidate request). For
Phase 1 this applies directly via the binding; the nested TryBoost veto
(Clear Body) is driver orchestration (doc 11 §3, Phase 2).
Fields
ScaleRelay
Set(relay.scale(num, den)) (doc 11 §1.1; minimon Sandstorm). when
gates the scale on a predicate (else Unchanged).
Fields
SetRelay(i64)
Set(Int(v)) — overwrite the relay with a constant int.
AddRelay(i64)
Set(Int(relay.as_int() + k)).
ClampRelay
Set(Int(relay.as_int().clamp(lo, hi))).
VetoIf
Fail when the predicate holds (the Clear-Body veto). silent ⇒
FailSilent (no “but it failed!” message).
ApplyTypeChart
Set(relay.scale(num, den)) from the type chart (doc 12 §3.1). Folds the
dual-type PRODUCT into ONE rational, then a single scale (doc 12 §5.3).
The in-flight move’s type is recovered from source_effect.
PayResource
Pay a resource cost (MP / SP / mana — doc 13 §4). If the selector cannot
pay amount of the named resource, the op Fails (the move is prevented via
the existing veto path, exactly like VetoIf); otherwise it deducts the
amount and passes the relay through. This expresses the cost gate in data
for a hook on BeforeMove. The deduction is pure arithmetic — no rng. The
resource name is interned to an id at LOAD (unknown ⇒ LoadError).
Fields
SetHp
Set the selector’s HP to a constant (blueprint 15 §2/§3, the new op).
OHKO writes SetHp(Foe, 0); Explode writes SetHp(Source, 0). The
when guard (ALL predicates hold) gates the write — OHKO authors
when: [LevelGE] so the KO lands only when the user’s level ≥ the foe’s
(bug #19); an empty when always applies. Pure write of ctx.battler.hp;
no entropy. (Unlike DamageFraction, this does NOT route through
take_damage — it is an absolute set, the faithful Gen-1 OHKO/Explode.)
Fields
SetDamage
Set ctx.mv.damage from a DamageValue source, bypassing the type
chart (blueprint 15 §2/§3, the new op). The special/fixed damage moves
(Seismic Toss = user level, Dragon Rage = 40, Sonic Boom = 20, Psywave =
rng·num/den·level) write the move’s damage directly so it rides the same
driver apply path as DealMoveDamage (the driver applies ctx.mv.damage
to the target after the ModifyDamage/Effectiveness fold). Authored on
ModifyDamage at a HIGH order so it overwrites the formula number. Pure
except DamageValue::RngScaledLevel, which draws ONE ctx.rng byte.
Fields
value: DamageValueThe damage value source.
DamageCurrentHpFraction
Damage the selector by a fraction of its CURRENT HP (blueprint 15
§2/§3, the new op). Super Fang = curHP/2 (floored at 1). Differs from
DamageFraction { of: CurHp } in that it ALSO writes ctx.mv.damage (so a
Substitute redirect / Counter read sees the real number) and floors a
non-zero result at 1 like the legacy (curHP/2).max(1). Pure read +
take_damage; no entropy.
Fields
RepeatHits
Re-apply the in-flight move’s damage N times — the Gen-1 multi-hit loop,
driven GAME-SIDE with NO engine change (blueprint 15 §2/§3 “RepeatHits”,
P4). Authored on DamagingHit, which the StackDriver fires AFTER the FIRST
hit’s take_damage (driver.rs resolve_action). So ctx.mv.damage is the
per-hit number the driver already applied ONCE; this op re-applies the SAME
number to target (N-1) MORE times — the faithful Gen-1 “compute damage
once, deal it N times” (no per-hit recompute). N comes from [count]:
Fixed(k)⇒ exactly k hits, NO byte (Double Kick / Bonemerang / the Twineedle double-hit);TwoToFive⇒ ONE byte folded by the legacydetermine_hit_countdistribution (themulti_hit_roll). [final_hit] runs Twineedle’s final-hit-only secondary (onechancebyte + guardedInflictStatus) AFTER the last hit, at the legacyside_effectordinal. This op needs NO engine seam: it loopstake_damageon the existingBattleCtx, drawing onlyctx.rng(one count byte + the optional final-hit chance byte) at its ordinal — so aScriptedRngreplays it identically andconsumed()is a pure function of the op-list.
Fields
target: SelectorThe selector to deal the repeated hits to (the defender — Target on a
DamagingHit hook).
final_hit: FinalHitRiderThe final-hit secondary (Twineedle poison); None for a plain multi-hit.
RemoveStatus
Clear the selector’s non-volatile status (the wuxia cleanse / 驱散
静心咒·天玑回春 op, affinity.md §六). Resolves the selector, then writes
ctx.battler_mut(who).status = None — a generic engine-field write, exactly
like SetHp writes .hp = value; no binding, no entropy. The
inverse of InflictStatus: where that sets a status,
this removes whatever non-volatile status is held. Resolves Unchanged (the
relay threads through). A game that never authors RemoveStatus is unaffected.