refactor: remove prototype crate - #16
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.