A Max speed — ACTIVATED ability can be activated at any speed, including speed 0. The prefix is parsed, recognized, and then dropped: the ability that reaches the battlefield carries no gate at all.
Reproduction
Howlsquad Heavy — Max speed — {T}: Add {R} for each Goblin you control.
Reported from a real game: the Goblin had been taken from its owner, and its new controller — at speed 2 — tapped it for red mana. The owner was at max speed, so the first hypothesis was that the engine reads the owner. It does not read anyone.
Measured through the real action pipeline (GameAction::ActivateAbility, then counting pips in players[].mana_pool), one variable changed per row:
| controller's speed |
owner's speed |
mana produced |
expected |
| 4 |
0 |
1 |
1 ✅ |
| 2 |
4 |
1 |
0 ❌ |
| 2 |
2 |
1 |
0 ❌ |
The third row is the one that names the defect: with nobody at max speed the ability still produces mana.
Dumping the parsed permanent confirms it directly — the ability arrives as
kind=Activated cost=Some(Tap) condition=None activation_restrictions=[]
Rules
- CR 702.178a: "Max speed — [Ability]" means "As long as your speed is 4, this object has '[Ability]'." The glossary spells out whose speed: that permanent's controller (or that card's owner, if it isn't on the battlefield). On the battlefield it is the controller, always.
- CR 702.179e: a player has max speed if their speed is 4 (CR 702.179d lifts the cap for Gomif-style effects).
Because the ability is absent while the gate is unmet, this is an activation restriction (CR 602.5) — not an intervening-if condition (CR 608.2c + the Shelldock Isle ruling, which this engine deliberately does not use for activation legality).
Where it is lost
parser/oracle.rs, Priority 4 (activated ability). The seam already recovers the typed gate from the em-dash prefix:
let aw_condition = strip_ability_word_with_name(cost_text)
.and_then(|(aw_name, _)| ability_word_to_condition(&aw_name));
…and then applies it for exactly one prefix:
if matches!(aw_condition, Some(StaticCondition::SourceIsHarnessed)) {
ir.shell.activation_restrictions.push(ActivationRestriction::SourceIsHarnessed);
}
StaticCondition::HasMaxSpeed is produced by ability_word_to_condition and then discarded. It has nowhere to go anyway: parser/oracle_condition.rs lists HasMaxSpeed in the exhaustive bridge's rejection group ("no exact restriction evaluator"), because ParsedCondition has no max-speed leaf.
This is only the ACTIVATED lane. Max speed — on a trigger (ability_word_to_trigger_condition) and on a static (layers.rs → StaticCondition::HasMaxSpeed → game::speed::has_max_speed) are both gated correctly today.
Scope
17 paper cards (Aetherdrift) print a Max speed — activated ability, so it is a class and not a one-off:
Amonkhet Raceway, Avishkar Raceway, Deviant Skytech, Endrider Catalyzer, Gas Guzzler, Glitch Ghost Surveyor, Goblin Surveyor, Hour of Victory, Howlsquad Heavy, Kickoff Celebrations, Leonin Surveyor, Loxodon Surveyor, Muraganda Raceway, Mutant Surveyor, Perilous Snare, Slick Imitator, Starting Column.
Three of the labeled abilities — Endrider Catalyzer, Howlsquad Heavy, Muraganda Raceway — are mana abilities, which is the worst case: they bypass the stack (CR 605.3a), so the ungated mana is simply in the pool with nothing to respond to.
Related but distinct: #1213 (Vnwxt, Verbose Host — the same keyword ungated on a replacement effect) was closed as fixed. That fix did not touch this lane.
Fix
PR attached. Four edits, all inside the existing single-authority seams:
ParsedCondition::HasMaxSpeed — a player-designation leaf, sibling of HasCityBlessing.
oracle_condition.rs — StaticCondition::HasMaxSpeed moves from the rejection group to the exactly-representable player-state group.
restrictions.rs — evaluated via game::speed::has_max_speed, the same authority layers.rs uses, so the CR 702.179d exception cannot diverge between the static and the restriction reading.
oracle.rs — the matches!(… SourceIsHarnessed) stamp becomes a small typed helper that distinguishes CR 207.2c ability words (no rules meaning, gate printed in the ability's own text — must stay ungated) from CR 702 keyword prefixes (∞, Max speed — the label is the whole gate).
I deliberately did not make the seam generic over every ability_word_to_condition entry: doing so would gate Threshold/Metalcraft/Delirium/Spell mastery/Revolt/Ferocious activations on a label that CR 207.2c says has no rules meaning, where the printed "Activate only as long as …" clause is already lowered by strip_activated_constraints.
A
Max speed —ACTIVATED ability can be activated at any speed, including speed 0. The prefix is parsed, recognized, and then dropped: the ability that reaches the battlefield carries no gate at all.Reproduction
Howlsquad Heavy —
Max speed — {T}: Add {R} for each Goblin you control.Reported from a real game: the Goblin had been taken from its owner, and its new controller — at speed 2 — tapped it for red mana. The owner was at max speed, so the first hypothesis was that the engine reads the owner. It does not read anyone.
Measured through the real action pipeline (
GameAction::ActivateAbility, then counting pips inplayers[].mana_pool), one variable changed per row:The third row is the one that names the defect: with nobody at max speed the ability still produces mana.
Dumping the parsed permanent confirms it directly — the ability arrives as
Rules
Because the ability is absent while the gate is unmet, this is an activation restriction (CR 602.5) — not an intervening-if
condition(CR 608.2c + the Shelldock Isle ruling, which this engine deliberately does not use for activation legality).Where it is lost
parser/oracle.rs, Priority 4 (activated ability). The seam already recovers the typed gate from the em-dash prefix:…and then applies it for exactly one prefix:
StaticCondition::HasMaxSpeedis produced byability_word_to_conditionand then discarded. It has nowhere to go anyway:parser/oracle_condition.rslistsHasMaxSpeedin the exhaustive bridge's rejection group ("no exact restriction evaluator"), becauseParsedConditionhas no max-speed leaf.This is only the ACTIVATED lane.
Max speed —on a trigger (ability_word_to_trigger_condition) and on a static (layers.rs→StaticCondition::HasMaxSpeed→game::speed::has_max_speed) are both gated correctly today.Scope
17 paper cards (Aetherdrift) print a
Max speed —activated ability, so it is a class and not a one-off:Amonkhet Raceway, Avishkar Raceway, Deviant Skytech, Endrider Catalyzer, Gas Guzzler, Glitch Ghost Surveyor, Goblin Surveyor, Hour of Victory, Howlsquad Heavy, Kickoff Celebrations, Leonin Surveyor, Loxodon Surveyor, Muraganda Raceway, Mutant Surveyor, Perilous Snare, Slick Imitator, Starting Column.
Three of the labeled abilities — Endrider Catalyzer, Howlsquad Heavy, Muraganda Raceway — are mana abilities, which is the worst case: they bypass the stack (CR 605.3a), so the ungated mana is simply in the pool with nothing to respond to.
Related but distinct: #1213 (Vnwxt, Verbose Host — the same keyword ungated on a replacement effect) was closed as fixed. That fix did not touch this lane.
Fix
PR attached. Four edits, all inside the existing single-authority seams:
ParsedCondition::HasMaxSpeed— a player-designation leaf, sibling ofHasCityBlessing.oracle_condition.rs—StaticCondition::HasMaxSpeedmoves from the rejection group to the exactly-representable player-state group.restrictions.rs— evaluated viagame::speed::has_max_speed, the same authoritylayers.rsuses, so the CR 702.179d exception cannot diverge between the static and the restriction reading.oracle.rs— thematches!(… SourceIsHarnessed)stamp becomes a small typed helper that distinguishes CR 207.2c ability words (no rules meaning, gate printed in the ability's own text — must stay ungated) from CR 702 keyword prefixes (∞, Max speed — the label is the whole gate).I deliberately did not make the seam generic over every
ability_word_to_conditionentry: doing so would gate Threshold/Metalcraft/Delirium/Spell mastery/Revolt/Ferocious activations on a label that CR 207.2c says has no rules meaning, where the printed "Activate only as long as …" clause is already lowered bystrip_activated_constraints.