-
Notifications
You must be signed in to change notification settings - Fork 0
Features en
中文 | English
Mechanics of every ported feature. For design motivation and implementation details see Architecture.
Each pattern is pre-compiled into bytecode (18 instructions: constant push, arithmetic, ceiling division, sub-pattern calls, stock extraction, output re-insertion) whenever the network recalculates. Execution is an ordered stack machine whose operand values are unbounded BigIntegers — overflow is impossible from the ground up. Small values (0–1023) are pre-allocated and powers of two take a fast path, keeping the common case cheap. There is no recursion anywhere in the pipeline; stack overflows cannot happen.
The first time a pattern executes, one real execution trace at "1 craft" scale is captured into a bundle; any later demand on the same pattern replays it scaled by n, in O(1). Sub-tree demands are carried as "needs" references rather than folded, so scaling never double-counts. Bundles persist per grid across requests.
The cache key is a composite of (output key, compiled bytecode): any pattern edit changes the bytecode fingerprint and invalidates stale bundles, and several patterns for the same output key can coexist. Before replay, the bundle also verifies that "the pattern each sub-call resolved to at capture time" (the recorded subChoices, merged transitively) still matches the current resolution — otherwise it re-captures. This closes the 1.12 equivalent of upstream's PatternRefreshReuse bug: after a player edits patterns, a stale cache could replay frozen sub-trees of patterns that no longer exist.
For Fibonacci-style exponential chains the VM does not expand nodes one by one; it performs a single O(patterns + edges) demand propagation over the pattern graph, and DAG nodes shared between branches replay only once. This is what makes 10^9-scale chains finish in milliseconds.
-
A + B → 2Aamplifiers: allocation is corrected by net gain; the self-key converges to a one-time seed. - A seedless self-reference reports exactly 1 missing startup unit — no more, no less.
Closed by-product loops (e.g. A → 2B → E + D → A) are seeded exactly under a working-capital model: forward simulation determines the required startup stock, with deadlock injection and Tarjan SCC for liveness. Lossy decreasing loops (e.g. 3A → 2B → D, where A degrades each cycle) report exactly 2 missing startup units. Every loop shape has a explainable minimal-seed semantics.
Pure conversion rings (value merely moving between keys, never growing) are validated with BigInteger fractional ring values; when seedless, the request reports exactly the lowest-value key missing instead of looping meaninglessly.
⚠ Experimental since 1.2.0, off by default (config:
ringSolverEnabled): the stall is root-caused on the faithful CPU simulator — the old net-form plans spared the delivery with stock and netted ring-member draws against the ring's own production, so plans shipped without startup inventory and deadlocked at t=0. The solver now follows faithful CPU semantics: the delivery must be CRAFTED (a real job ignores the requested item's own stock), every member's net draw plus priming floors is charged into usedItems, and idle folds bill their out-of-ring draw; plans also emit a task order the simulator itself validated (orders that cannot bootstrap under the CPU's strict task-priority scheduling are rejected), and catalyst seeds bill as priming; every ring scenario now completes on the simulator (see GAP-4b in the roadmap). Beyond rings, EVERY plan's task order is now CONSTRUCTED by topological sort of the pattern dependency graph — rings keep their probed firing order (the cycle cut at the priming edges); deep plans are correct by construction with no verification skip, and the faithful CPU replay is demoted to a fallback assertion. Live validation is ongoing — feedback is welcome. Settled on the live server (2026-09-19): the AE2UEL source confirms (CraftingCPUCluster.injectItems:265) that the final output is booked as delivered the moment it is crafted and NEVER re-enters the CPU inventory (return leftover; // ignore it.); all four ring shapes and the startup-capital bill were verified item-by-item against a real-server trace, and a live E-case expansion hole was found and fixed (see "External-ingredient recursion").
Mutual rings that net-grow per round (e.g. 4 spirits → ingot, 1 ingot → 12 spirits, netting 8 spirits per round — the family originates from a real modpack recipe, the Gaia Spirit loop, encountered in actual play). The native planner produces NO plan for such rings: propagation only schedules the dissolve side and the CPU stalls on missing ingots, while the native recursion planner simply crashes with a stack overflow. The ring solver folds it algebraically: pattern counts are solved to the material-minimal least fixed point and the whole ring collapses into a single gross-flow bundle (emissions inserted before extractions, in-ring turnover self-consistent), with the startup working capital disclosed separately and exactly. The whole family is covered:
- Byproduct intermediates: a ring routed through a key that only exists as a byproduct (the solver's T4 fallback brings the intermediate into the graph);
- External-input rings: inputs without a producer inside the ring are ordinary ingredient demand, shortfalls reported honestly;
- Byproduct-rooted orders: ordering the byproduct directly sizes the request by the byproduct's own per-craft output and drives the whole ring;
- Coupled ring chains: consumer rings solve first and their solved net draws write back into the supplier rings' floors, so suppliers size themselves on the amplified demand.
Honest scope — please read: AE2UEL CPUs deliver the final output the moment it is crafted — it never re-enters the CPU inventory (CraftingCPUCluster.injectItems :265). Consequences:
- Ordering the loop's own net-gain product (e.g. ordering gaia spirits from the gaia loop): every produced unit leaves immediately, so the synthesis side's consumption can only come from the job's startup capital, and the full gross draw is billed as such (10000 spirits ordered = 5000 spirits of startup capital; a shortfall is reported exactly) — there is no "small stock runs a big order";
- Forms whose gain flows through intermediates DO bootstrap: ordering the ring's upstream/downstream products, byproduct-rooted orders and coupled ring chains route the ring's gain through intermediates that are not siphoned and can circulate, bootstrapping from minimal capital; existing stock also serves as dissolve feedstock within one batch (1 ingot dissolves → 12 spirits → crafts 3 ingots);
- No regeneration within one batch: deliveries never flow back, so startup capital scales linearly with the order size (net-gain-product orders). Players can batch manually — each batch's deliveries return to the network and become the next batch's feedstock, scaling every round — with the solver's disclosed net-gain rate (e.g. +8 per round) as the sizing basis; automatic engine-side batch progression is on the roadmap, not yet implemented.
Net-losing cycles, pure conversion rings and catalyst loops are not the ring solver's job — they are handled with exact semantics by the lossy working-capital model, the conversion conservation check and the catalyst seed machinery respectively; the gates keep every family in its place.
The upstream amount × ceil(times / uses) amortization cannot hold on 1.12: the AE2UEL CPU extracts processing inputs by exact key and never re-consumes the worn return, so amortized plans stall mid-job after burning the charged tools. Since 1.2.0 a durability-tool input is charged as an ordinary gross input (one fresh tool per firing), with the worn return riding along as an inert by-product; the returned-input detection logic remains, feeding catalyst seeds and ring analysis. See Differences & Roadmap.
When two outputs are each other's inputs (e.g. steel dust ↔ steel ingot) with no stock of either and no external supply, neither pattern should ever be scheduled — the vanilla planner would pick one and report it missing. During resolution the VM runs an SCC analysis over the recipe exchange graph: self-loops are always pruned; an SCC is pruned only when it is seedless and externally unfed. "External supply" means a producer of an off-ring key outputs a ring member (sibling candidates do not count as supply; by-product inflow does). Seeded, externally fed and back-flowing rings are all kept. Single-candidate contention skips the whole analysis, and a runtime guard backstops the pre-pruner. Upstream has carried the same capability in its mainline since v1.13.12 (cycle-aware pattern selection, originating from the GTL CYCLE-AWARE feature); this port is an independent implementation, pinned by ten dedicated tests.
A (request key, amount, pattern-set version) → plan cache turns repeated identical requests into microsecond-level hits. A hit is never trusted blindly — the live stock is re-verified (planned consumption must still be covered, past shortfalls must have been filled) before returning. Any pattern-set change is sensed through a global version (AtomicLong): the grid mixin bumps it when the pattern-set identity fingerprint changes, and the bundle and plan caches are cleared together. Removing an intermediate pattern forces the request to report missing rather than replay a stale chain.
A mixed assignment from the solver is adopted only after one real replay confirms strictly fewer total missing items; model distortion can only fail the confirmation and keep the original plan. The solver can never make a plan worse.
In the "plan shows missing → player restocks → recalculate" scenario, a missing key that becomes satisfiable under the new stock is genuinely re-executed instead of replaying the stale shortfall.
Multi-pattern assignment (see the solver page)
When one output has several patterns, greedy single-pick can over-demand some leaf. On shortfall the VM launches a solver: enumerate → split-weight local search → synthesize a virtual pattern expressing the mixed ratio (e.g. 5 crafts = 4×A + 1×B), subject to the confirmation gate. This closed upstream's only remaining FALSE_POSITIVE (multi-dag/fibonacci/minimum, carried since v1.9.6) with zero missing.
Fluids are represented as fake item keys; compilation normalizes packet form to the canonical drop form (mB carried on the AE stack size). The native fluid channel is authoritative when present, and AE2FC's own item-channel fakes are excluded to avoid double counting. Deep fluid sub-patterns — craftable fluids with partial stock — resolve correctly.
Scaled* "double/scaled" wrapper patterns are defensively unwrapped (reflective getOriginal, depth cap 8); bytecode and plan keys anchor to the original pattern, so third-party scaling add-ons work out of the box.
Substitution groups (called replacement groups in upstream docs — the 1.12 API name is canSubstitute) + FUZZY_SLOT slot markers; processing patterns default to a same-item fuzzy family. Exact-slot and fuzzy-slot demands are separate: substitute stock only ever satisfies fuzzy slots, never exact ones. Fuzzy family lookups have two key-cached layers (sandbox family lists + the VM's composed family = substitution group ∪ NBT family), cutting the fuzzy scenario from 17.7 ms to 4.75 ms.
Slot substitution is a crafting-pattern feature (narrowed in 1.2.0): AE2UEL hard-wires the substitute flag to crafting patterns (canSubstitute = isCrafting && nbt, PatternHelper :87) and its CPU consults substitutes in the craftable branch only — a processing pattern's slots are always exact keys. The compiler follows the same semantics: processing slots are no longer substitute-filled, substitute stock cannot satisfy them, and shortfalls are disclosed as missing (the previous behavior produced plans a real CPU could never execute — a t=0 deadlock). Crafting patterns with substitution enabled are unaffected; after upgrading, a few previously-"craftable" requests may now honestly report one missing item.
Out-of-ring consumption after a fold (e.g. the gaia loop's terrasteel), if it has a pattern of its own, is recursed into that producer: every level simulates stock first and schedules only the deficit, injections are POST-ORDER (an input's producer is injected before the consumer itself, so the replay applies in dependency order), cycle re-arrivals are cut and fall back to honest missing, and pattern-less keys keep raw-ingredient shortfalls (conservative fallback). The live gaia trace exposed the one-level version of this expansion silently dropping the injected amplifier's own mana draws from used AND missing (an unschedulable silent hole, caught by the always-on invariant audit as MISSING-COVERAGE / INPUT-REACH) — fixed by the recursion.
The VM plan builds a LiteCraftTreeNode display tree directly (via a @Pseudo mixin), keeping AE2CT's crafting previews working.
-
AE2VMCrafting.isLoaded()/calculateSync()(synchronous calculate; upstream is CompletableFuture-shaped — see Differences) -
getBatchRemainder()for requests beyond Long.MAX_VALUE -
pickBestPatternminimum-output guard, ignore-fix, T1 exact / T3 fuzzy resolution
Machine-sourced auto-orders are planned by the VM BY DEFAULT — the native tree cannot carry net-gain rings or fluid patterns, so stock-keeper style devices silently stopped auto-ordering on such webs while the same manual order succeeded (the native fallback was the root cause). The rare foreign planner that needs the native tree structure is excluded via the nativeTreeSourceMarkers config (class/package substring); an explicit AE2VMCraftingRegistry.register call from a mod always wins back the VM.
New patterns compile immediately when the grid recalculates (CraftingGridCacheMixin), so the first request never pays compilation cost.
Anything the VM cannot handle falls back to the vanilla recursion tree; proxyEnabled disables the whole proxy.
相关链接