Skip to content

refactor: remove prototype crate - #16

Merged
andrewgazelka merged 1 commit into
mainfrom
refactor-remove-prototype
Mar 21, 2024
Merged

refactor: remove prototype crate#16
andrewgazelka merged 1 commit into
mainfrom
refactor-remove-prototype

Conversation

@andrewgazelka

Copy link
Copy Markdown
Member

No description provided.

@andrewgazelka
andrewgazelka merged commit 9612af9 into main Mar 21, 2024
@andrewgazelka
andrewgazelka deleted the refactor-remove-prototype branch March 21, 2024 22:49
andrewgazelka added a commit that referenced this pull request Jul 28, 2026
…place them (#1030)

Effects primitives for Super Smash Mobs: the engine layer an ability
composes out of. No `events/smash/` changes — the seam
(`Server::particles`, deleting `Cue`) follows as a second, small PR so
the abilities agent rebases once and deliberately.

## The particle table is generated, and checked against Mojang's encoder

`minecraft:particle_type` is the one registry the general registry
codegen (#1021) deliberately skips, and the exclusion note there names
this script. It cannot emit this one: `ParticleTypes.STREAM_CODEC` is a
dispatch, so `nix/extract-protocol.py` marks it `unresolved` and
`protocol.json` carries only the 125 names — not which entries write a
body, nor what shape.

`nix/generate-particles.py` reads the dispatch table out of the
decompiled Java, where every `register` call names its option class
beside the particle, and reads each option class's own `StreamCodec` to
learn the body. It emits two enums following #1021's conventions exactly
— `#[repr(u16)]`, discriminant is the network id, `const fn id`,
`name()` off a static table, `from_id`/`from_name` at the decode
boundary only, closed, no `Unknown`:

- `ParticleKind` — the id on its own, 125 unit variants.
- `Particle<'a>` — the type together with its options. Thirteen payload
shapes.

There is no `Particle::from_id`, and the module says why: a type id
alone does not say how many bytes follow, so turning one back into a
particle means reading the body.

**The reading of the Java is only as good as the parse, so it is checked
against the encoder it was parsed from.** `nix/java/VanillaEncoder.java`
now drives Mojang's own `ParticleTypes.STREAM_CODEC` over all thirteen
shapes, and `tests/play_particles.rs` compares byte for byte, both bare
and inside `level_particles`:

```
particle.block                 -> 0101
particle.item                  -> 36c407030000
particle.dust                  -> 15ffff000040000000
particle.dust_color_transition -> 16ffff0000ff0000ff3fc00000
particle.entity_effect         -> 1c80336699
particle.vibration             -> 37000000004000003ffe28
particle.trail                 -> 383ff8000000000000c002000000000000400e000000000000ff00ff001e
```

Anything the script does not already understand is a hard failure rather
than a skipped entry — a particle silently omitted would encode as
another particle's id the next time the registry shifted.

`nix run .#sync-minecraft-particles` regenerates;
`checks.minecraft-particles` fails on drift, alongside the other six.

## `hyperion::effects`

```rust
Effect::burst(particle, at) / line(from, to) / ring(c, r) / disc(c, r) / sphere(c, r)
    .count(n).offset(spread).speed(v).points(n).normal(n).long_distance(b)
    .emit(world)                                   // one bundle, one broadcast
    .expanding(to, seconds).emit(world)            // a ParticleEmitter entity a system ages

apply_impulse(entity, delta) / set_velocity(..) / knock_back(entity, from, magnitude, lift)
knockback_impulse(origin, target, magnitude, lift) -> Vec3
quantized(v) -> Vec3                               // what the client will actually see

players_within(world, center, radius, except) -> Vec<Hit>   // nearest first
nearest_player(..) -> Option<Hit>;  Hit::falloff(radius)

spawn(world, kind, at) -> EntityView
launch(world, kind, at, velocity, shooter) -> EntityView
Lifetime::new(seconds)                             // expires and despawns itself
```

Only particles is a flecs `Module`, because only particles have state
outliving the call that started them. The emitter ages on flecs' delta
time, not on any game clock, so an effect started in the hub does not
freeze — same reason #1023's `Effect` did.

`smash::module::knockback::strength` stays where it is and stays the
only health-scaled knockback in the game; this layer takes an
already-computed magnitude.

## Two bugs found while building it

**Nothing outside `PacketState::Play` ever sent `RemoveEntities`.**
`remove_player_from_visibility` is gated on it, so a projectile, a
dropped item or a turret the server destructed stayed drawn on every
client for the rest of the match. `SpawnModule` now has the observer for
everything else.

**`HyperionCore` never imported `SpatialModule`,** although
`update_projectile_positions` (via `EgressModule`) calls
`spatial::get_first_collision`, which reads the `SpatialIndex`
singleton. A world that spawned an engine projectile panicked with
`Component hyperion::spatial::SpatialIndex is not registered` on the
next tick. bedwars imported it by hand; smash did not, so the panic was
one arrow away. Found by the new spawn test, which panicked exactly that
way before the import went in.

## What I did not do

- **No e2e gate for the new shapes.** A gate needs something to *emit*
an effect, and nothing does until the seam PR lands. The wire assertions
here are byte-level against Mojang's own encoder, which is more precise
about content than an e2e is, but it is not a client seeing a packet.
The e2e peer belongs with the seam PR and I will add it there.
- **The area query is a scan, not the BVH.** `SpatialIndex` is rebuilt
empty every frame because nothing in production adds `Spatial`;
ENG-10879. The fence is in `area.rs`'s module comment with the count it
holds at (sixteen players, fine; a thousand, not).
- **No `SoundEvent` enum.** Positional sound at an arbitrary point
already works and I was not going to build a second path for it. The
1968 string sound ids remain.
- `nix/generate-rust.py:288` refers to `nix/generate-entity-types.py`,
which #1024 deleted. Not my file, not touched.

## Every new guard broken, and what it said

Eighteen. Each mutation applied, the test run, the output recorded, the
mutation reverted; full suite green again afterwards.

| # | Mutation | Failure |
|---|---|---|
| 1 | dust colour as a `VarInt`, not a fixed int | `dust
(minecraft:dust) does not encode the way the jar does` / left `[21, 128,
128, 252, 255, 15, ...]` right `[21, 255, 255, 0, 0, ...]` |
| 2 | `Dust = 200`, an id drifted from the registry | `assertion left ==
right failed: dust` / left `200` right `21` |
| 3 | `BlockStateId::from_raw` accepts anything | `assertion failed:
BlockStateId::from_raw(-1).is_err()` |
| 4 | ring basis seeded from a fixed axis | `out of the plane by 3 with
normal Vec3(1.0, 0.0, 0.0)` |
| 5 | sphere bunched toward one pole | `64 of 64 above the equator` |
| 6 | disc drawn as a ring | `0 of 64 in the inner half` |
| 7 | area query forgets to exclude the caster | left `[Entity(906),
Entity(907)]` right `[Entity(907)]` |
| 8 | area query stops sorting by distance | left `[Entity(910),
Entity(909), Entity(908)]` right `[Entity(909), Entity(910),
Entity(908)]` |
| 9 | knockback steered by vertical separation | `Vec3(0.3577709,
1.1155418, 0.0)` |
| 10 | `quantized` reports what was asked, not what was sent |
`Vec3(0.8, 0.4, 0.0) did not survive the wire` / left `Vec3(0.7999756,
0.3999878, 0.0)` right `Vec3(0.8, 0.4, 0.0)` |
| 11 | `long_distance` dropped before the packet | `long_distance sets
it` |
| 12 | `offset` dropped before the packet | left `(0.0, 0.25, 0.125)`
right `(0.5, 0.25, 0.125)` |
| 13 | committed particle table hand-edited | `committed particle table
is stale; run: nix run .#sync-minecraft-particles` plus the diff |
| 14 | generator meets a codec it has never seen |
`GeyserBaseParticleOptions: 'ByteBufCodecs.FLOAT' is a codec this script
has never seen. Read its Java and add it to PRIMITIVES, COMPOSITES or
ID_MAPPERS; guessing at a body shifts every byte after the particle.` |
| 15 | Mojang renames the `register` helper | `125 registry entries have
no register() call in ParticleTypes.java, starting with
['minecraft:angry_villager', ...]; the regex no longer matches every
declaration` |
| 16 | `launch` forgets the owner | `an arrow with no owner collides
with the bow that fired it` / left `None` right `Some(Entity(919))` |
| 17 | a lifetime never expires | `outlived its lifetime` |
| 18 | an entity destructed the tick it spawns | `expired before its
time` |

**Two of these were wrong the first time, and only running them showed
it.** #2 and #10 as first written failed to *compile* rather than
failing the assertion, which proves nothing about the guard; both were
rewritten as mutations that build. And #16 first "passed" — the mutation
had matched the same text inside a doc comment above the code, so the
mutation harness (`replace(..., 1)`) had silently edited a comment and
left the real line alone. Re-anchored on a unique two-line span, it
fails as above. A mutation that does not compile and a mutation that
does not apply look identical to a green test run.

## Checks

- `nix run .#test` — 714 passed, 1 skipped
- `nix run .#lint` — clean for everything in this diff
- `nix run .#fmt` — clean
- `checks.minecraft-particles`, `checks.minecraft-encoder-fixtures`,
`checks.minecraft-proto-json` — green

**`origin/main` at `a25c3a9` is already red on lint**, from #1023:
`events/smash/tests/abilities.rs:595` has an unused `use
flecs_ecs::prelude::*;`. Verified by stashing this branch's changes and
linting that target at `a25c3a9`. Not touched here — the `bow` worktree
already has the one-line deletion staged.

## Also filed

- ENG-10879 — the spatial BVH is rebuilt empty every tick; nothing in
production adds `Spatial`.
- ENG-10893 — `git stash` is shared across worktrees, so one agent's
`pop` takes another agent's stash. Nearly lost a colleague's
work-in-progress to it.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant