Nine integration tests: the compiled named-hook + crate + data fixture, unchanged bytes and mtimes, two mods' crates under --locked, and every new diagnostic. A docs page for 0.6.0, a roadmap row and README notes. The boundary plan snapshot no longer records the local rustc. Co-Authored-By: Claude Opus 5.5 <[email protected]>
13 KiB
SpongePowered Mixin feature map and remaining work
Status: Horizon Mixins 0.6.0. This is a capability comparison and design backlog, not Java annotation, bytecode or mod compatibility. The implementation uses Rust source before Cargo compilation; Sponge Mixin integrates Java bytecode with the runtime classloading pipeline. Sponge overview.
The source links below point to upstream documentation/annotation definitions. Rust design notes are this project's proposals, not Sponge guarantees. Missing features are not accepted TOML kinds. Unknown fields/kinds still fail validation.
Implemented counterparts
| Sponge capability | Horizon counterpart | Supported boundary |
|---|---|---|
| HEAD callback | HEAD | Explicit typed target-parameter context |
| Cancellable callback | HEAD, BEFORE_CALL, AFTER_CALL, AFTER_ASSIGN with cancellable = true |
ControlFlow returns from the target; whole-expression/statement proofs for interior sites |
| Return callbacks/result replacement | RETURN | All normal explicit/implicit exits, including cancellation; no individual return selectors, async, ? or closure exits |
| INVOKE callback | BEFORE_CALL | Exact syntax; only whole statements, plain let initializers and block/function tails |
| Overwrite | OVERWRITE | Entire body; exclusive against every other injection |
| ModifyArg | MODIFY_ARG | One selected argument; explicit index and same input/output type |
| ModifyConstant | MODIFY_CONSTANT | Exact runtime numeric/bool/char/string literals; no compile-time expressions or implicit comparison constants |
| ModifyArgs | MODIFY_ARGS | Typed tuple of all explicit call arguments; separate borrowed receiver and explicit owned/unit result |
| ModifyVariable | MODIFY_VARIABLE | Plain local/incoming parameter identities; initializer/simple assignment STORE and primitive scalar value LOAD |
| Local capture | Explicit $local, $local_ref, $local_mut bindings |
Plain let declarations with ordinals and lexical visibility checks; Cargo checks moves/borrows; no surrogates |
| Slices | Optional call-selected from/to bounds | Pristine expression interval, same lexical block; inclusive start/exclusive end; explicit boundary counts/ordinals |
| Accessor / Invoker | GET, GET_MUT, SET, INVOKE bridges | Public namespaced inherent methods on owned nongeneric structs; fields and exact existing inherent/trait calls |
| Priorities, ordinals and count validation | priority, ordinal, require | Deterministic ID tie-breaks; require is an exact original match count here |
| After INVOKE / INVOKE_ASSIGN | AFTER_CALL / AFTER_ASSIGN | Owned/unit result borrows before consumption, or post-statement lexical captures after the store/temporary drops |
| MixinExtras ModifyExpressionValue | MODIFY_EXPRESSION_VALUE | Exact syntax expression selectors and explicit value types; runtime values and scalar reads, excluding places |
| Unique instance fields | Namespaced state fields/getters | Owned named nongeneric structs, explicit constructors and derived serde defaults; custom saves remain explicit |
| MixinExtras WrapOperation | WRAP_OPERATION | Chained FnOnce continuations; module-level free calls or borrowed parameter receivers; owned result/unit |
See advanced-injections.md and
scopes-and-bridges.md for the actual TOML API,
composition phases and compilation-tested limits. Sponge callbacks offer broader
site selection and cancellation. Its require is a minimum rather than our exact
match count. Callback documentation.
Missing injection and selection features
| Feature | What Sponge provides | Rust design needed |
|---|---|---|
| TAIL and individual RETURN selection | Final bytecode return, or a chosen return ordinal | Define Rust source exit identities; TAIL is not an alias for every return and must not silently omit early exits |
| FIELD, NEW, JUMP sites | Field accesses, allocation/construction and branches | Distinguish place expressions from values, struct literals from constructors, and branches from enclosing expressions |
| Redirect | Replace calls, field/array operations, object creation and selected type checks | Independent conflict rules; preserve places, indexing evaluation, borrow order, receiver effects and replacement types |
| Broader argument/variable support | Broad bytecode-level argument/local selection | Extend proven Rust cases beyond borrowed parameter receivers, plain lets, simple stores and scalar loads; preserve borrowing, moves and places |
| Pattern capture / surrogates | Supply locals and alternative compatible handlers | Conditional/destructured pattern identities, broader return contexts and explicit fallback rules; plain let capture is implemented |
| Broader slice landmarks | General injection-point slice bounds | Current bounds are calls in the same lexical block; branches, arbitrary expressions and cross-scope bounds need separate contracts |
| Shifted, custom and multi-target selectors | Relative instruction offsets, custom injection points, wildcard/multiple method targets | Source-level equivalents with explicit cardinality, deterministic expansion and ambiguity checks |
References: injection point reference, Redirect, ModifyArgs, ModifyVariable, local capture, Slice, custom injection points.
AFTER_CALL now compiles owned/unit adapters and Drop/cancellation cases; borrowed results are rejected. AFTER_ASSIGN can capture stored borrowed results through ordinary Rust types. The local-variable fixture demonstrates that two bindings with the same name cannot be confused. A redirect prototype must reject competing replacements before staging. None should infer method identity from a bare method name.
Missing member and type transformations
| Feature | Current gap | Proposed Rust boundary |
|---|---|---|
| Shadow | No declarative placeholders for existing fields/methods | Explicit member contracts validated in the target module; local helpers already have ordinary same-crate privacy |
| Broader accessor / invoker support | Nongeneric owned structs only; unsafe/async/const/generic signatures and unresolved associated projections rejected | Extend signature/owner resolution with compiled privacy/lifetime examples; current bridges add no dependency edges |
| Broader fields / methods and Unique | Namespaced owned instance state, support modules and generated getters/bridges are implemented; arbitrary members and generic state owners are missing | Extend explicit constructor, layout, derive and serialization contracts with compiled cases |
| Interface / soft-implements mixins | No generated trait implementations or duck-interface adapters | Rust coherence/orphan rules, trait dependencies, associated items and existing impl conflicts must be checked by Cargo |
| Intrinsic proxies / superclass behavior | No equivalent proxy mechanism | Define explicit delegation to original functions; Rust has no direct Java superclass model |
| Final / Mutable controls | No Java-style field finality transformation | Rust mutability and interior-mutability rules remain enforced; an annotation must never promise to bypass borrowing |
References: Shadow, accessor/invoker generation, Unique, member merging, interfaces and architecture, Final, Mutable.
Instance state uses explicit constructor coverage, rejects repr/open external construction, and supplies serde defaults for missing fields. Custom saves and entity-ID side tables remain explicit mod code. See state contracts.
Missing configuration, tooling and compatibility features
| Feature | Current gap / relevant direction |
|---|---|
| Conditional configs and plugins | No client/server config selection, programmatic shouldApplyMixin or companion plugins. Prefer deterministic declarative feature/side conditions first. |
| Optional/virtual targets and fallback groups | Missing targets always fail. Optionality and alternatives would need explicit declarations and group-level cardinality checks; never silently skip mandatory hooks. |
| Group min/max, expect/allow and constraints | We have exact require per selector, not group-level alternatives or separate debug expectations. |
| Refmaps / obfuscation mapping | Exact pinned Rust paths only. Cross-revision adapters may be useful; Java obfuscation mapping is not presently needed. |
| Annotation ergonomics | TOML only; Rust attributes could become syntax sugar over the same validated IR. |
| Expanded diagnostics and development tools | We have JSON diagnostics, plans, source snapshots and Cargo logs. No bytecode verifier/export, IDE plugin, interactive site explorer or hot-reload workflow. |
| Runtime loader integration | No classloader analogue, launcher integration, binary patcher or running-game hot replacement. Native Rust changes require compilation. |
References: configuration, configuration plugins, Group, reference maps, Sponge tooling entry point.
MixinExtras is a separate comparison
Operation wrapping and expression-value modifiers come from the separate MixinExtras project. Our WRAP_OPERATION follows the general chaining model but uses typed Rust FnOnce and our own manifests/order rules; it does not load Java MixinExtras mods. WrapOperation documentation.
MODIFY_EXPRESSION_VALUE now selects exact original syntax and preserves
once-only evaluation and short-circuit behavior. Place expressions, implicit
borrowed owned reads, compile-time values and unproven control flow are excluded.
See effects and state and upstream
ModifyExpressionValue documentation.
Suggested next feature work
- Exercise the supported operations on real horizon-world state in an isolated pinned checkout and widen receiver/context support based on compiled cases.
- Widen borrowed-result adapters only with lifetime and destruction-order proofs.
- Add optional declarative compatibility groups and extend existing receiver, pattern and bridge support only for compiled use cases.
- Consider redirects and broader member/trait transformations with independent semantics and conflict tests.
Launcher/build-cache work is tracked separately in roadmap.md. Feature parity should be judged by tested modding use cases, not by copying annotation names onto syntax transformations with different semantics.
Version 0.4.0 also supports lifetime-bearing impls and HEAD over preserved
closure/? bodies; see native consumer contracts.
The newer state extension adds explicit constructor rewriting separately. Other
body-rewriting kinds retain their existing safety limits.