Skip to content

feat(smash): every ability draws something, on the press - #1086

Merged
andrewgazelka merged 3 commits into
mainfrom
feat/every-ability-is-visible
Jul 30, 2026
Merged

andrewgazelka merged 3 commits into
mainfrom
feat/every-ability-is-visible

Conversation

@andrewgazelka

Copy link
Copy Markdown
Member

tools/smash-match.py joins a real Minecraft client to a real server, presses
every ability in the roster and refuses one that sends no level_particles.
It named 23. A 24th, Zombie / Bile Blaster, was passing only when a thrown
blob happened to land on somebody.

Measured before and after, on a real client, on x86_64-linux:

before after
particle failures 23 0
total sweep failures 25 2

The two that remain are Cow / Mooshroom Madness and Guardian / Target Laser
on buffs_melee. Those are a harness measurement defect, not a game defect,
and are filed as ENG-11399 with the evidence: the repaired game bench, which
now calls the same function a real attack packet calls, passes both.

What it does

The cheap fix would be a visuals::blast in each. That passes the gate and
leaves the game where it started: a player who sees the same grey puff for a
bow, a web, an ink cloud and a tidal wave has learnt nothing from any of them.
So visuals gains a vocabulary, and each entry picks the particle vanilla
already uses for the thing depicted, so a player who has played Minecraft
reads it without being taught.

Shape carries meaning. An aura is a ring and not a disc, because the only
fact a player needs from an aura is whether they are inside it. A lunge is a
line from where it began to where it ended. A wave is a rising wall at its
own radius.

Two of these are not decoration. Ice Path is documented as laying ice blocks,
the seam cannot write blocks, so the ability was a small hop and nothing else;
the drawn path is a player's only evidence it exists. Arctic Aura is the same
at radius 5. The Snowman and the Wolf had no visual identity at all.

Sustained ultimates draw on their beat. A twenty second mode that draws once
is a twenty second window a victim cannot see they are inside.

And the draw happens on the press

The second commit is the part Bile Blaster forced. Seven abilities drew only
after some contingent later event, usually a projectile connecting, so the
gate decided their fate on a coin flip. The draw now happens on the press
itself, above any branch a miss or a failed lookup can skip. Guardian's Target
Laser is the clearest: it returns if nobody is in range, so it draws at the
caster first and only then a beam to whoever got marked.

A test that could not fail, fixed and watched failing

Found while chasing the two buffs_melee abilities.
tests/abilities.rs::melee_damage read the kit's base damage, read
MeleeBonus, added them, and dealt exactly that much, then checked the damage
had gone up. It compared one copy of the formula against another copy of
itself. Deleting the bonus lookup from the swing path would have left every
buffs_melee assertion green forever.

It now calls input::melee_damage, the function a real attack packet calls.
Proved rather than asserted: with the bonus lookup removed from that function,
every_declared_effect_actually_happens goes red; restored, green. Under the
old helper that deletion was invisible.

Verified: cargo test -p smash 292 tests green across 27 binaries, and the
real-client sweep above.

(sent by an AI agent via Claude Code)

@github-actions github-actions Bot added the feat label Jul 30, 2026
`tools/smash-match.py` joins a real client, presses every ability in the
roster and refuses one that sends no `level_particles`. It named
twenty-three. This is those twenty-three.

The cheap fix would be a `visuals::blast` in each. That passes the gate
and leaves the game where it started: a player who sees the same grey puff
for a bow, a web, an ink cloud and a tidal wave has learnt nothing from any
of them. So `visuals` gains a vocabulary instead, and each entry picks the
particle vanilla already uses for the thing being drawn -- `crit` for a bow
release, `item_cobweb` for webbing, `item_slime` for a slam, `mycelium` for
mooshroom spores, `angry_villager` for a rage mode, `sweep_attack` for a
lunge. A player who has played Minecraft reads them without being taught.

Shape carries meaning with it. An aura is a ring and not a disc, because
the only fact a player needs from an aura is whether they are inside it,
and a ring is the boundary that answers exactly that. A lunge is a line
from where it began to where it ended, because the distance covered is the
ability. A wave is a rising wall at its own radius.

Two of the twenty-three are not decoration. Ice Path is documented as
laying ice blocks, the seam cannot write blocks, and so the ability today
is a small hop and nothing else; the drawn path is the only evidence a
player has that anything happened. Arctic Aura is the same at radius 5.

Sustained abilities draw on their beat and not once at the cast. A
twenty-second ultimate that draws one puff is a twenty-second window a
victim cannot see they are inside.

Separately, and found while chasing the two abilities that declare
`buffs_melee` and fail the same sweep: `tests/abilities.rs::melee_damage`
could not fail. It read the kit's base damage, read `MeleeBonus`, added
them, and then dealt exactly that much -- comparing one copy of the formula
against another copy of the same formula. Deleting the bonus lookup from
the swing path would have left every `buffs_melee` assertion green. It now
calls `input::melee_damage`, which is what a real attack packet calls.

Watched failing before being trusted: with the bonus lookup removed from
`input::melee_damage`, `every_declared_effect_actually_happens` goes red;
restored, green. Under the old version that deletion was invisible.

The repaired test then clears both accused abilities, so the `buffs_melee`
disagreement is between the game bench and the real client rather than in
the game. That is a separate and smaller bug, still open.

Verified: `cargo test -p smash` is 292 tests green across 27 binaries.
@andrewgazelka
andrewgazelka force-pushed the feat/every-ability-is-visible branch from e4908a2 to e5faf5f Compare July 30, 2026 03:12
Seven abilities drew nothing at the moment of the press. Each passed the
real-client particle gate only when some later contingent event happened to
draw: usually a projectile landing on somebody.

`Zombie / Bile Blaster` is the one that proved it. It passed one CI run and
failed the next with nothing between them: in the passing run a blob
connected and the particle appeared at the victim's feet, and in the failing
run every blob missed. The gate had been deciding that ability's fate on a
coin flip, and the coin came up tails.

So the rule this commit adds is not "every ability draws", which the
previous commit already established. It is that **the draw happens on the
press itself**, above any branch that a miss or a failed lookup can skip.
Guardian's Target Laser is the clearest case: it looks for the nearest
player in range and returns if there is none, so anything drawn after that
lookup is a visual whose presence depends on the game state rather than on
the button. It now draws at the caster first, unconditionally, and draws a
second beam to the marked player once there is one.

Nine additions to the vocabulary, each picking the particle vanilla already
uses for the thing depicted, on the same terms as the rest of that file.
Sustained ultimates draw on their beat: a twenty second mode that draws once
is a twenty second window a victim cannot see they are inside.

This closes the class rather than the instance. Of the eight abilities whose
particle was contingent, the seven fixable ones are fixed; the eighth,
Wither Image, already draws on the press.

Verified: `cargo test -p smash`, 292 tests green across 27 binaries. The
real-client sweep is the instrument that named this and is the one that has
to confirm it.

Written by a subagent; the placement above the early return was the one
thing I insisted on and it did it in every case.
@andrewgazelka
andrewgazelka merged commit 35897d4 into main Jul 30, 2026
1 check passed
@andrewgazelka
andrewgazelka deleted the feat/every-ability-is-visible branch July 30, 2026 03:13
@github-actions

Copy link
Copy Markdown

Benchmark Results for general

ray_intersection/aabb_size_0.1                     [  18.6 ns ...  18.6 ns ]      -0.11%
ray_intersection/aabb_size_1                       [  18.6 ns ...  18.7 ns ]      +0.06%
ray_intersection/aabb_size_10                      [  18.7 ns ...  18.6 ns ]      -0.42%
ray_intersection/ray_distance_1                    [   1.3 ns ...   1.3 ns ]      +0.34%
ray_intersection/ray_distance_5                    [   1.4 ns ...   1.4 ns ]      -0.02%
ray_intersection/ray_distance_20                   [   1.3 ns ...   1.3 ns ]      +0.66%
overlap/no_overlap                                 [  16.5 ns ...  16.6 ns ]      +0.58%
overlap/partial_overlap                            [  15.9 ns ...  16.0 ns ]      +0.59%
overlap/full_containment                           [  14.5 ns ...  14.5 ns ]      -0.17%
point_containment/inside                           [   5.4 ns ...   5.4 ns ]      +0.09%
point_containment/outside                          [   5.7 ns ...   5.7 ns ]      -0.12%
point_containment/boundary                         [   5.4 ns ...   5.4 ns ]      -0.08%

Comparing to 5c7f127

@github-actions

Copy link
Copy Markdown

Benchmark Results for general

ray_intersection/aabb_size_0.1                     [  18.6 ns ...  18.6 ns ]      -0.21%
ray_intersection/aabb_size_1                       [  18.7 ns ...  18.7 ns ]      +0.30%
ray_intersection/aabb_size_10                      [  18.7 ns ...  18.6 ns ]      -0.49%
ray_intersection/ray_distance_1                    [   1.3 ns ...   1.3 ns ]      -0.04%
ray_intersection/ray_distance_5                    [   1.3 ns ...   1.3 ns ]      -0.06%
ray_intersection/ray_distance_20                   [   1.3 ns ...   1.3 ns ]      -0.14%
overlap/no_overlap                                 [  15.5 ns ...  15.5 ns ]      -0.01%
overlap/partial_overlap                            [  15.9 ns ...  16.1 ns ]      +1.21%
overlap/full_containment                           [  14.6 ns ...  14.6 ns ]      -0.18%
point_containment/inside                           [   5.4 ns ...   5.4 ns ]      -0.11%
point_containment/outside                          [   5.7 ns ...   5.7 ns ]      -0.48%
point_containment/boundary                         [   5.4 ns ...   5.4 ns ]      +0.17%

Comparing to 5c7f127

andrewgazelka added a commit that referenced this pull request Jul 30, 2026
…r the class (#1093)

Two abilities put nothing on any screen when you pressed them, and the
check
that would have caught them could not, for a reason worth writing down.

## The two

**Wither Image** is two presses: the first plants a decoy where you
stand, the
second swaps you back to it. Only the swap drew. Planting was silent, so
an
opponent could not see that the escape was armed — which is the entire
threat
the ability carries. It now plants a column of soul fire, deliberately
unlike
the portal shell every teleport uses, so the two presses are told apart
at a
glance.

**Blaze's Inferno** is a half-second-cooldown flame cone and drew
nothing at
all. The only fire on screen came from the burn on a victim, so spewing
at open
air was invisible and a player could not see where their own flame
reached. It
now draws a jet along the facing, above the victim search, because the
search
is what can come up empty.

Both are the pattern #1086 was supposed to finish. That commit message
says
*"the eighth, Wither Image, already draws on the press."* It does not,
and never
did — I read the second press for the first.

## Why no gate caught it

Every sweep we have, the mock one here and the real-client one in
`tools/smash-match.py`, presses each ability **up to three times**,
because two
abilities in the roster genuinely need more than one. That is correct
for the
question those sweeps ask and is exactly why neither could ask this one:
the
second press supplied the particles and the first press's silence was
invisible.

So `every_ability_draws_on_the_press_that_fires_it` presses **once**,
and once
only.

## The near-miss in the gate itself

Written first against particles alone, it named seven abilities and
**only one
was a defect**:

```
"Iron Golem / Iron Hook", "Spider / Needler", "Creeper / Sulphur Bomb",
"Wither Skeleton / Guided Wither Skull", "Blaze / Inferno",
"Chicken / Chicken Missile", "Wither Skeleton / Wither Image"
```

Six of those answer the press by putting a **drawn object** in the world
— a
skull, an egg, a thrown block — and for those the object is the picture.
A
check that calls correct code broken is the same error in a mirror, and
it is
the worse direction: somebody satisfies it by bolting a redundant puff
onto six
working abilities. It now asks whether the press put anything at all on
a
screen, by either route, and stays one test because "which abilities are
the
projectile kind" is not a list anybody should maintain.

## Watched to fail

```
without the Wither Image fix, particles-only:  7 named, 1 real
without the Wither Image fix, widened:         ["Wither Skeleton / Wither Image"]
with both fixes:                               ok. 13 passed; 0 failed
```

`cargo test -p smash` green across all 21 test binaries; `cargo fmt
--all
--check` and `cargo clippy -p smash --all-targets` both clean.

## Second commit: clippy has been red on main since #1086

`cargo clippy -p smash --lib` exits **101** on `origin/main` at
`ee1139e`:

```
error: this function could have a `#[must_use]` attribute
  --> events/smash/src/input.rs:260:8
```

One attribute, from making `melee_damage` public in #1086. The attribute
is not
the interesting part — **nothing noticed for four hours**, because #1088
turned
every workflow to `workflow_dispatch` and clippy only ever ran there. It
is not
in the nix flake gate, so `nix run .#ci`, the thing anybody actually
runs before
pushing, is green on a tree clippy rejects.

Same hole as ENG-11424, which is the same sentence about `cargo fmt
--all
--check`. Both belong in the gate; turning CI off is what made that
urgent
rather than tidy.

---
🤖 Opened by Claude Code (claude-opus-4-6).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant