Design: decide, fire, apply: route inventory
Stage 1 of #784: evidence and inventory only. This stage adds no runtime code, tokens or admissions. References below identify the existing owners in this worktree (feat/decide-fire-apply, from arismoko/horizon main f84b4be037); the older numbers in decide, fire, apply are corrected here. Vanilla 26.3 was queried with python tools/rea_vanilla.py method <class> <method>; its decompiled text is not reproduced, only described. NeoForge could not be checked (see NeoForge), so no counterpart contract is assigned to any row.
From the modding book of arismoko/horizonbfbda6a
On this page
How the list was found
Two mechanisms reach the mod API from client-lib code, and both were enumerated:
- Catalog targets.
rg -n 'horizon_client_lib::' crates/horizon-mod-vocab/src/hooksreturns 22target = "horizon_client_lib::…"attributes in 19 declaration files. The generator classifies them incrates/horizon-mod-api-gen/src/lib.rs:344(free_function_source, prefixhorizon_client_lib::) and resolves each target'snative_<hook>indispatch.rs:46(emit). - The callback table. 19 of the 22 are installed through
NativeClientHooks(crates/horizon-client/src/native_client_hooks.rs:9, installed atentry.rs:225); the other 3 callmod_api::dispatch_themselves (crates/horizon-client/src/crafting/native_hooks.rs:14,40,56).
Two consequences a reviewer should keep in mind:
- Every client-lib target currently names its function explicitly. None is implied by the
Game::native_<name>default, so the design's "targets without an explicit annotation" set is empty today. This was checked againstcatalog.rs:363,508(the default is a Game method) andhook_targets.rs:41(resolution only rewrites Game native-hook modules). Re-runrg -n 'default_target|entry\("target"\)' crates/horizon-mod-api-gen/src/catalog.rsand readhook_targets.rsas well as the explicit-target and callback-table searches; searching explicit annotations alone does not establish the implicit set. - None of the 22 receives a
Host. 20 take&LoopContextand the two data-load collections (ingredient,recipe_property_sets) take&LoadContext.LoopContextis documented as holding "no game, world handle or mutable registry access" (crates/horizon-world/src/native_load_context.rs:189). The mutating ones instead write&mutout-parameters. That is the Host-less shape the clean break replaces.
Client-lib target inventory
Shape is the current dispatch shape; Treatment is this inventory's recommendation.
| Hook | Declaration | Client-lib entry | Side / contract | Shape | Treatment |
|---|---|---|---|---|---|
living_ | hooks/ | motion.rs:52 | both, listen | plain notice | motion exclusion |
living_ | hooks/ | motion.rs:59 | both, listen | value transform (&mut f32) | motion exclusion |
living_ | hooks/ | motion.rs:67 | both, listen | value transform (&mut f32) | motion exclusion |
living_ | hooks/ | motion.rs:75 | both, listen | value transform (&mut f32) | motion exclusion |
block_ | hooks/ | motion.rs:83 | both, listen, block Part | value transform (&mut f32) | motion exclusion |
block_ | hooks/ | motion.rs:91 | both, listen, block Part | value transform (&mut f32) | motion exclusion |
fluid_ | hooks/ | motion.rs:140 | both, listen | value transform (&mut [f64; 3]) | motion exclusion |
player_ | hooks/ | motion.rs:128 | both, listen | boolean query, written back | motion exclusion |
is_ | hooks/ | motion.rs:100 | both, listen, block Part | boolean query, written back | read-only fire |
item_ | hooks/ | menu.rs:388 | both, cancel | cancel + slot/cursor write-back | typed decision |
menu_ | hooks/ | menu.rs:615 | both, listen, menu Part | query + write-back (may_, max_) | typed decision |
living_ | hooks/ | hunger.rs:23 | both, cancel | value transform + cancel | typed decision |
living_ | hooks/ | breathing.rs:34 | server, listen | query + write-back (3 out-params) | typed decision |
living_ | hooks/ | breathing.rs:69 | server, cancel | query + write-back + cancel | typed decision |
living_ | hooks/ | landing.rs:45 | both, cancel | value transform + cancel | typed decision |
farmland_ | hooks/ | landing.rs:31 | server, cancel | cancel notice | typed decision |
ender_ | hooks/ | mobs/ | server, cancel | target write-back + cancel | typed decision |
ingredient | hooks/ | crafting/ | both, collect, data load | collect (Vec<String>) | read-only fire |
ingredient_ | hooks/ | crafting/ | both, listen | pure boolean query at craft time | read-only fire |
recipe_ | hooks/ | crafting/ | both, collect, data load | collect (additions) | read-only fire |
player_ | hooks/ | lan/ | both, collect | collect (Text) | read-only fire |
player_ | hooks/ | lan/ | server, collect | collect (Option<Text>) | read-only fire |
Counts: 22 targets, 19 files. 8 are the design's excluded motion values, is_ladder is a ninth motion-module target the design does not list (see Open items), 8 are mutating gameplay moments that need a typed decision, and 6 (including is_ladder) are pure queries/collections that need only a read-only fire boundary.
Menu routes
The read sets and bold outcome names below are migration proposals, not existing token validation. Existing callbacks mutate state directly; cancellation does not currently imply transaction rollback.
Paths are relative to the repository root. hooks/ is crates/horizon-mod-vocab/src/hooks/ and client/ is crates/horizon-client/src/. The design's line numbers moved; these are current.
item_(local pickup)stacked_ on_ other - Current owner
client/menu.rs:3396 Menu::, fires atpickup :3418; event recordclient/menu/ models.rs:28 - Vanilla method mirrored
AbstractContainerMenu.doClick, PICKUP branch- Read set
- player uuid and entity id, slot index, backing container and its index, primary button, cursor stack, clicked slot stack, the slot's
may_/place may_/pickup allow_, live writes throughmodification SlotView - Existing public result per outcome
- cancel skips the bundle override, and a cancelled
handled(the event's cancellation result) also skips the plain pickup; uncancelled, the bundle answer decides and an unhandled click takes the ordinary pickup;clickreturnsOk(())either way (menu.rs:3422) = Canceled/Applied; nothing reachesInvalidInput
item_(authoritative)stacked_ on_ other - Current owner
client/lan/ menu.rs:909 pickup, fires at:955; slot viewlan/menu.rs:557 - Vanilla method mirrored
- same, server side
- Read set
- same, plus the guest's carried stack and the menu's registry id
- Existing public result per outcome
- same cancel/
handledsplit (lan/), then the guest resync (menu.rs:959 lan_)crafting.rs:380
menu_(local)slot_ rules - Current owner
client/menu.rs:1874 slot_(firesrules :1885); top-up loopmenu.rs:2565move_(firesto :2592)- Vanilla method mirrored
Slot.mayPlace,Slot.getMaxStackSize(not a pickup-permission hook)- Read set
- menu registry id, backing slot index, stack id and components and count, the slot's own vanilla answer, the item maximum, container limits (
menu.rs:1897) - Existing public result per outcome
- refusal →
may_false: the carried stack is not placed, but the click itself can still apply, because a slot stack that matches the carried stack is taken onto the cursor anyway (place pickup_,inner menu.rs:3487); so a placement refusal is Canceled only for the placement, not for the whole click, andclickreturnsOk(())either way; clamp to 1..=99 (menu.rs:637,CONTAINER_atMAX :428) is Applied
menu_(LAN)slot_ rules - Current owner
client/lan/ menu.rs:470 slot_(firesrules :493)- Vanilla method mirrored
- same
- Read set
- same, resolved against the backing container or player inventory (
lan/)menu.rs:486 - Existing public result per outcome
- placement refusal and bounded capacity answer as above; override applied at
lan/menu.rs:506
local click, cursor, throw, swap
- Current owner
client/menu.rs:3204 click;:3396pickup,:3433pickup_,inner :3510swap,:3683pickup_all - Vanilla method mirrored
AbstractContainerMenu.clicked→doClick- Read set
- slot count, cursor, every touched slot stack, input mode and button, quick-craft state
- Existing public result per outcome
Err(OUT_=OF_ BOUNDS) "IndexOutOfBoundsException"(menu.rs:659) → InvalidInput;Ok(())→ Applied or Canceled
local quick move
- Current owner
client/menu.rs:2645 quick_; landing setmove :1837quick_move_ landing - Vanilla method mirrored
AbstractContainerMenu.quickMoveStack(abstract; menus override) andmoveItemStackTo- Read set
- source stack, destination ranges and roles, per-destination capacity via
slot_, registry idmax - Existing public result per outcome
- no error path: no movement returns
None; successful movement normally returns the original clicked stack (menu.rs:3180), with remainder stored back in the source; individual refused destinations are skipped
local drag / quick craft
- Current owner
client/menu.rs:3626 quick_,craft_ accepts :3642quick_, drag branch ofcraft_ preview clickat:3213- Vanilla method mirrored
doClick, QUICK_CRAFT stages 0/1/2- Read set
- drag status, drag type, dragged slot list, cursor, per-slot
may_and maximumplace - Existing public result per outcome
- preview is read-only and cannot commit; the commit is the ordinary click branch (InvalidInput/Applied as above)
local pick-all
- Current owner
client/menu.rs:3589 quick_,move_ all :3600quick_,move_ all_ each :3579can_take_ for_ pick_ all - Vanilla method mirrored
AbstractContainerMenu.canTakeItemForPickAll- Read set
- target slot rules and the cursor stack
- Existing public result per outcome
- refused slots are skipped silently (Canceled), no error
LAN authoritative clicks and backing storage
- Current owner
client/lan/ menu.rs:813 click;:909pickup,:729quick_,move :1125swap,:1225pickup_,all :1283quick_; storagecraft Slots::read :280/write:306,still_valid :236- Vanilla method mirrored
AbstractContainerMenu.clicked/doClickon the server, reached throughhandleContainerClick- Read set
- guest uuid and carried stack, authoritative menu generation and window id, menu registry id, backing container identity (
still_), per-slot read/write throughvalid Slots - Existing public result per outcome
lan_(inventory_ click lan_) answers throughcrafting.rs:327 inventory_(clicked lan/) andcontainers.rs:746 answer_(click :782): a stale state id sends the whole menu; otherwise mismatched or unclaimed changed slots and the cursor are corrected. This reconciles prediction after the click, rather than refusing a frozen action; no error packet, and Busy has no surface today
recipe-book placement
- Current owner
client/game/ network/ lan_ crafting.rs:637 lan_,place_ recipe :866lan_,check_ recipe :919lan_; firesapply_ recipe hooks/atplayer_ recipe_ book_ click.rs:13 :671- Vanilla method mirrored
ServerPlaceRecipe.placeRecipe(the menu overload, which returnsRecipeBookMenu.PostPlaceAction)- Read set
- recipe id resolved from the display index, its placement, guest craft grid, every source slot,
use_, creative, known recipesmax - Existing public result per outcome
- unknown or impossible recipe and unknown placement drop the packet (
:662); a ghost plan is answered explicitly (:677), the existing counterpart ofPostPlaceAction.GHOST→ refused/no-change
mod crafting placement
- Current owner
client/game/ network/ lan_ crafting.rs:1104 lan_,mod_ craft_ apply :989lan_crafting_ click - Vanilla method mirrored
ServerPlaceRecipe.placeRecipeover a mod menu- Read set
- mod menu registry id, linked container slots, recipe placement, input slots
- Existing public result per outcome
Nonewhen the menu is gone → refused; the click itself still returns handled
recipe transfer
- Current owner
client/game/ network/ lan_ crafting.rs:759 guest_,recipe_ transfer :802guest_recipe_ check - Vanilla method mirrored
ServerPlaceRecipe.placeRecipe(client-initiated placement)- Read set
- recipe id, all source slots, destinations,
all - Existing public result per outcome
TransferError::and the other transfer results are encoded in the reply (Unavailable :785) → refused/no-change
anvil prepare and take
- Current owner
client/menu.rs:1389 anvil_,created :1395anvil_,update :1407anvil_,updated :2066anvil_,undo :2080anvil_,cancel :2091anvil_; ordered sequencetake client/–game/ network/ lan_ stations.rs:172 :254; hookshooks/,anvil_ craft_ pre.rs:13 hooks/,anvil_ craft_ post.rs:11 hooks/anvil_ update.rs:21 - Vanilla method mirrored
AnvilMenu.createResult,AnvilMenu.onTake- Read set
- slot 0, slot 1, rename text, cost data slot,
only_,renaming repair_, result slot, player levels and inventory, cursor, the anvil block statecount - Existing public result per outcome
- Pre cancel →
anvil_restores result, inputs, cost, inventory and cursor (cancel(undo) lan_) whilestations.rs:190 clickreturnsOk(())= Canceled; Post is a notice; the update answer replaces output and cost, or cancels to no result
grindstone prepare and take
- Current owner
client/menu.rs:1418 grindstone_,place :1431grindstone_,placed :1446grindstone_; ordered sequencetaken client/–game/ network/ lan_ stations.rs:205 :254; hookshooks/,grindstone_ place.rs:18 hooks/grindstone_ take.rs:19 - Vanilla method mirrored
GrindstoneMenu.createResult; the take hook mirrors the result slot's take handling in that class- Read set
- slot 0, slot 1, result slot, enchantments (experience), cursor
- Existing public result per outcome
- place cancel → result slot emptied (Canceled); take cancel → result taken with no experience, sound or input reset (
menu.rs:1452)
menu close and merchant settlement
- Current owner
client/menu.rs:3865 merchant_(close MerchantMenu.removed); ordinary container noticeclient/,game/ modding/ native_ menus.rs:257,269 hooks/; separate mod-menu noticeplayer_ container_ close.rs:21 native_,menus.rs:307 hooks/menu_ closed.rs:12 - Vanilla method mirrored
AbstractContainerMenu.removed,MerchantMenu.removed- Read set
- cursor stack, payment slots 0 and 1, player inventory
- Existing public result per outcome
- no result shape:
player_follows ordinary container close;container_ close menu_is specifically for a mod-opened menu.closed merchant_settles cursor and payments throughclose place_; see vanilla evidence below for the living/connected versus dead/disconnected distinctionback
merchant state
- Current owner
client/menu.rs:3723 merchant_,update :3766merchant_,take :3794merchant_,select :3831merchant_; firesfill client/,game/ items/ trading.rs:543 client/; hooksgame/ network/ lan_ trading.rs:458 hooks/,trade_ select.rs:11 hooks/trade_ with_ villager.rs:28 - Vanilla method mirrored
MerchantContainer.updateSellItem,MerchantResultSlot.onTake,MerchantMenu.tryMoveItems- Read set
- offers, selection hint, payment slots, player inventory 3..39, result slot, future experience
- Existing public result per outcome
- listen and collect only, so no cancel surface; the collect answer is written back and stands
Two structural notes for stage 2:
- The local click path mutates in place (
menu.rs:3204): there is no point where a decision could be validated after listeners and before the write. - The LAN coordinators take the menu out of storage before dispatching, with different results for a recursive click. The crafting table (
lan_crafting.rs:996–:1002) finds no menu and stops. The inventory menu (lan_crafting.rs:341–:345) is rebuilt empty withunwrap_or_else, so a recursive inventory click continues on a fresh menu. Neither answers an admission Busy.
The Game-only mod-menu click hooks are not additional client-lib targets: menu_clicked (native_hooks/base.rs:3368, fired at game/modding/native_menus.rs:306) and menu_quick_move (native_hooks/base.rs:1615, fired at game/modding/api_values.rs:2020) are Game hooks, not client-lib targets, and are migrated with the rest of the server menu path.
Baseline caller fixtures
crates/horizon-client/src/menu/dfa_tests.rs pins three stage-2 acceptance rows through today's public Menu::click / Menu::place_recipe callers, without installing listeners or adding runtime code:
- Cursor stone 3 into an empty chest slot: cursor empty, destination stone 3.
- Quick move stone 10 into matching stacks of 60 and 58: each reaches 64 (adds 4 and 6), source empty, other chest slots and cursor unchanged.
- Place the vanilla stone pressure plate recipe from two stone: inventory source empty, exactly two grid stacks of one stone, output one plate. Taking that output consumes the grid once; a second empty-result click produces no additional plate. Placement stages ingredients; the result take consumes them. These are distinct operations in vanilla.
These are Rust caller regressions with independent vanilla expectations, not a new Java/Rust parity harness or proof of dispatch counts, tokens, cancellation, stale refusal or rollback. The recipe fixture is ignored by default and asserts that resources were actually loaded when explicitly requested. Run on the build server with prepared resources at .local/horizon-client/26.3:
cargo test -p horizon-client-lib --lib dfa_baseline_ -- --include-ignoredThe sources live under horizon-client, but client_lib.rs owns the menu module and its unit tests. The recipe test reads HORIZON_TEST_RESOURCES, or .local/horizon-client/26.3 in the checkout when that is unset, and fails rather than passing silently when neither has the prepared data.
Vanilla evidence
Each row is a method this page cites, queried with python tools/rea_vanilla.py method <class> <method> from the 26.3 client jar. For overloaded methods select each --overload-index. Inventory classes use net.minecraft.world.inventory; ServerPlaceRecipe uses net.minecraft.recipebook, the packet listener uses net.minecraft.server.network, and LivingEntity uses net.minecraft.world.entity. The supplied data was read from the sibling horizon-pp .local via a process-local lab.LOCAL override, with scratch HORIZON_HOME and JDK 25. No game files are included here. REA's Java output for moveItemStackTo contains contradictory branches despite its complete status; the matching/empty-slot branches were cross-checked with JDK javap -c -p on the same jar.
AbstractContainerMenu.clicked- What it does (in words)
- Runs
doClicksynchronously inside onetry; an exception becomes a crash report carrying menu type, menu class, slot count, slot, button and input type. - Result
- complete
AbstractContainerMenu.doClick- What it does (in words)
- The click itself: drag staging for QUICK_CRAFT, then PICKUP (placement and take rules, safe insert/take,
onTake), QUICK_MOVE throughquickMoveStack, SWAP, CLONE and THROW. Reads the cursor and the slots it touches. - Result
- complete
AbstractContainerMenu.quickMoveStack- What it does (in words)
- Abstract: the base class declares it and each menu supplies its own routing, so there is no single vanilla algorithm to mirror.
- Result
- no body (abstract)
AbstractContainerMenu.canTakeItemForPickAll- What it does (in words)
- Default answer is true; menus that restrict pick-all override it.
- Result
- complete
AbstractContainerMenu.moveItemStackTo- What it does (in words)
- Moves a stack into a slot range: first fills stacks with the same item and components up to each slot's maximum, then empty slots that accept the item, walking forwards or backwards as asked; the empty-slot pass stops after the first insertion; reports whether anything moved.
- Result
- REA + bytecode cross-check
AbstractContainerMenu.removed- What it does (in words)
- Server players only: a non-empty carried stack is dropped or placed back in the inventory and the carried slot is then emptied.
- Result
- complete
ServerGamePacketListenerImpl.handleContainerClick- What it does (in words)
- Runs on the server thread, resets the action time, drops a click for another container id, resyncs spectators and dead players, drops clicks on an invalid menu, logs and drops an out-of-range slot, then calls
clicked. - Result
- complete
Slot.mayPlace- What it does (in words)
- Default answer is true; subclasses restrict placement.
- Result
- complete
Slot.mayPickup- What it does (in words)
- Default answer is true; subclasses restrict taking.
- Result
- complete
Slot.getMaxStackSize- What it does (in words)
- The no-argument default is the container maximum; the stack overload also caps it at the item's maximum.
- Result
- bytecode cross-check
Slot.safeInsert- What it does (in words)
- The single-argument overload delegates with the whole count; the bounded overload refuses placement when disallowed, otherwise splits into an empty slot or merges matching stacks within capacity, returning the remainder.
- Result
- both overloads complete
ChestMenu.quickMoveStack- What it does (in words)
- Player inventory sources route to the chest range forwards; chest sources route to player slots backwards. The source is emptied or updated, and success returns the original stack copy.
- Result
- complete
ServerPlaceRecipe.moveItemToGrid- What it does (in words)
- Removes the requested matching ingredient count from inventory into a grid slot (or grows its stack), returning the still-missing count; no matching source returns -1.
- Result
- complete
ResultSlot.onTake- What it does (in words)
- Removes one item from each occupied recipe cell and handles recipe remainders. Two stone cells therefore consume two stone on one result take.
- Result
- complete
AnvilMenu.createResult- What it does (in words)
- Builds the result from the two input slots and the rename text, computing repair, enchantment and naming cost,
only_and the material count; writes the result slot, sets the cost data slot and broadcasts; empties the result when the cost is 40 or more without infinite materials.renaming - Result
- complete
AnvilMenu.onTake- What it does (in words)
- Charges the levels, uses up the material or input, clears the cost, sends the rename text through the server text filter, clears the left input, then with a 12% chance damages or breaks the anvil, always playing the use sound.
- Result
- complete
GrindstoneMenu.createResult- What it does (in words)
- Writes the result computed from the two inputs into the result slot and broadcasts.
- Result
- complete
MerchantContainer.updateSellItem- What it does (in words)
- Uses the second payment as the first matching candidate when the first is empty (without swapping stored slots); with nothing to pay it clears the result and future experience, otherwise matches an offer against the payments (retrying with them swapped), writes the result and its experience unless the offer is out of stock, and tells the client whether a result exists.
- Result
- complete
MerchantResultSlot.onTake- What it does (in words)
- Check-take achievements first, then for the active offer takes the payment in either order, tells the merchant, awards the trade statistic, writes the shrunken payments back and adds the offer's experience to the villager.
- Result
- complete
MerchantMenu.tryMoveItems- What it does (in words)
- Ignores an out-of-range index, sends each payment stack back to inventory slots 3..39 and gives up if one does not fit, then fills the payment slots from the selected offer's costs once both are empty.
- Result
- complete
MerchantMenu.removed- What it does (in words)
- Calls the base
removedfirst, clears the trading player, returns on the client. On the server, for a dead or disconnected player it drops the two payment stacks; for a living connected player it returns them to the inventory (placeItemBackInInventory). - Result
- smali (Java body unavailable)
ServerPlaceRecipe.placeRecipe- What it does (in words)
- Two overloads. The menu overload first answers
PostPlaceAction.NOTHINGwhen the grid cannot be cleared and dropping is disallowed; otherwise it gathers inventory/grid contents and callstryPlaceRecipe, returning itsRecipeBookMenu.PostPlaceAction. The private one takes the recipe and the stacked contents and places after checking the grid stacks can still grow. - Result
- both complete
LivingEntity.onClimbable- What it does (in words)
- Spectators and gliding entities inside glide-through blocks are refused; a block in the climbable tag, or a usable trapdoor over a ladder, is accepted and its position remembered; otherwise false.
- Result
- complete
The three baseline expected values follow the pickup/insert rules, chest routing plus stack top-up, and recipe placement plus result-taking above. The pressure plate's two-stone shape and single-item output were checked in the supplied 26.3 data/minecraft/recipe/stone_pressure_plate.json.
NeoForge
The issue names no NeoForge counterpart. The web reader could not access the 26.3 menu Javadoc or stacking event Javadoc during this review. Other listed counterparts were not independently verified. No assertion that all paths or versions return a particular HTTP status is made. No NeoForge source or patches were read. Every counterpart contract below remains unverified; no equivalence is claimed.
| Hook or route | Public counterpart named in the catalog | Public page | Status |
|---|---|---|---|
item_ | ItemStackedOnOtherEvent | 26.3 Javadoc path | unverified |
anvil_, anvil_ | AnvilCraftEvent.Pre, .Post | 26.3 Javadoc path | unverified |
grindstone_, grindstone_ | GrindstoneEvent.OnPlaceItem, .OnTakeItem | 26.3 Javadoc path | unverified |
menu_, player_ | PlayerContainerEvent.Close | 26.3 Javadoc path | unverified |
trade_ | TradeWithVillagerEvent | 26.3 Javadoc path | unverified |
is_ | IBlockExtension.isLadder | 26.3 Javadoc path | unverified |
menu_, menu_, menu_, trade_ | none (vanilla method overrides) | 26.3 Javadoc path | unverified |
| Menu click, cancellation and transaction contract | AbstractContainerMenu | 26.3 Javadoc path | unverified |
Review coverage
The explicit search still finds 22 targets in 19 files; callback fields and direct crafting dispatches account for all 22. Default-target construction and resolution introduce no implicit client-lib target. Spot-checks included all nine motion entry lines, both menu hook entries, both breathing entries, all three crafting entries, both player-name entries, the LAN read/write/validity owners and the close notification owners. Every vanilla evidence method was re-queried (overloads separately); the added Slot.getMaxStackSize evidence was checked directly in bytecode. Book links from SUMMARY and the design note resolve relative to their containing Markdown files.
Open items
- Read sets and versions. No action decision epoch was identified in these routes. Network menu state ids already exist (
client/lan/containers.rs:797), but serve prediction reconciliation, not complete ABA validation.set_container_limits(client/menu.rs:1897) is a setter, not a validation check; LANSlots::still_valid(client/lan/menu.rs:236) checks backing storage and reach. Stage 2 must establish complete action mutation epochs; the read-set column above is the proposed starting set per route. - Reentrancy and close.
menu_closedfollows a mod-opened menu close (native_menus.rs:307); ordinary container close usesplayer_container_close(native_menus.rs:269) and no route has an admissionBusy. The LAN paths take the menu out of storage, but only the crafting table (lan_crafting.rs:996) then stops a recursive click; the inventory path (:341) rebuilds an empty menu and continues, and the local path clicks in place. Each route's public refusal forBusymust be chosen before code: local click has onlyOk(())/IndexOutOfBoundsException, LAN has the resync, recipe placement has the transfer result byte. - Budgets. There are none today; a quick move walks every destination slot (
menu.rs:2565) and a recipe plan is unbounded. Limits must be measured from real plans, as the design says. - Clamp or reject. The 1..=99 slot clamp (
menu.rs:637,CONTAINER_MAXmenu.rs:428) and the anvil update's material-cost clamp (negative to zero,hooks/anvil_update.rs) are existing documented clamps to preserve; structural errors already surface asIndexOutOfBoundsException. - Complete route evidence. For the two menu dispatch targets,
item_stacked_on_otherhas explicit cancellation, and its cancel has the bundle/handledsplit above.menu_slot_rulesis a query. The ordered anvil/grindstone sequence is ingame/network/lan_stations.rs, not inmenu.rs, so the design's owner list understates it. The server entry intoclickedis now checked (handleContainerClick); the individual station algorithms' downstream callers are not. - Host. No target takes a
Host: 20 take&LoopContext(native_load_context.rs:189) and the two data-load collections take&LoadContext;is_ladder's declaration says so explicitly. AddingHostonce, in the decide/fire step, avoids a second signature break later. is_ladder. It is a ninth motion-module client-lib target but not one of the design's eight excluded value transforms. It is a per-block boolean query, so this inventory recommends a read-only fire, not a token; the design must say whether it joins the motion exclusion.- NeoForge. Until a public 26.3 page is reachable, no counterpart contract (cancel semantics, transaction boundary, thread) can be assigned to the menu or station hooks.
Edit this page on GitHub · Spotted something wrong? Open an issue .