Feature Container Contents Menu - bh679/dungeon-train-mc GitHub Wiki

World-space container contents menu

Since: v0.74.0 β€” PR #103 v0.799.0 β€” PR #1304 β€” works in the Train Builder, and resolves from the container you are looking at rather than the plot you are standing in. See Β§ Train Builder support.

A billboarded worldspace UI for authoring weighted item pools that roll into chests / barrels / dispensers / hoppers / shulker boxes / any block with a Container BlockEntity. Tap the container-contents hotkey (default C) while looking at a container in any editor plot and a panel anchored against the targeted face appears, listing every authored item with its count and weight. At carriage spawn time, each container's slots roll the pool and produce a deterministic, world-seed-keyed inventory.

Mirrors the architecture of the block-variant menu (Z key) shipped in v0.56.0, with a simpler feature set β€” no rotation, no lock-IDs, no clipboard copy. The two menus coexist on different keys and store their data in separate sidecar files.

When to use which

Goal Tool
Choose which block spawns at this position (chest vs barrel vs stone) Tap Z β†’ block-variant menu
Choose which items spawn inside the container at this position Tap C β†’ container-contents menu
Add the held block as a position variant Hold Z + right-click
Add the held item to the container's loot pool Tap C β†’ menu β†’ Add

The two menus operate on the same (plotKey, localPos) namespace but write to separate JSON files β€” <plotKey>.contents.json for container pools, <id>.variants.json for block variants. Editing one never affects the other.

Tap-to-toggle

The C key uses the same tap-to-open pattern as Z. Press + release within ~400ms with no use-interaction during the press β†’ opens the menu against the block under the crosshair. Tap C again (or click X in the toolbar) to close. Held-state is also reported to the server (parallel to the Z key's hold flag) so future "hold C and click" flows have a hook, but no behaviour depends on it yet.

The keymap is rebindable from Esc β†’ Options β†’ Controls β†’ Dungeon Train β†’ Container Contents Menu.

Toolbar

5 cells, fixed left-to-right: Add | min N | max M/27 | Clear | X.

Cell Action
Add Capture the player's main-hand item and append it as a pool entry. Count comes from the held stack size; weight defaults to 1. Empty hand β†’ action-bar warning ("Hold an item or use the search screen to add").
min Click +1, shift-click -1. Sets the minimum number of slots filled at spawn time. Auto-clamped so min ≀ max (when max is not "all"). Wraps at the boundaries: click + at the upper bound (= max when set, else 64) β†’ 0; shift-click at 0 β†’ upper bound. Display: bare integer (e.g. 1).
max Click cycles all β†’ 0 β†’ 1 β†’ 2 β†’ … β†’ 64 β†’ all, shift-click reverses. Display: 5/27 or all/27 β€” the /27 is the targeted block's actual Container.getContainerSize() (chests=27, barrels=27, hoppers=5, dispensers=9, shulkers=27, etc.). When the user lowers the max ceiling below the current min, min is dragged down with it.
Clear Drop the entire pool from the sidecar. Container in the world is untouched (only the spawn-time roll is affected).
X Close the menu. Re-tapping C does the same.

Per-row cells

Each row in the grid lists one pool entry. Right-to-left cell layout: [Γ— remove] [w weight] [x count] [item name].

Cell Action
item name Display only. Shows the item registry path segment (e.g. diamond_sword from minecraft:diamond_sword).
xN (count) Click +1, shift-click -1. Range [1, 64] with wrap at the boundaries: click + at 64 β†’ 1; shift-click at 1 β†’ 64. Count is a max β€” at roll time, the actual stack size for this entry is rolled uniformly in [1, count] per slot, with an independent deterministic mixer. So x6 means "up to 6 per slot," not "always 6".
wN (weight) Click +1, shift-click -1. Clamped to β‰₯1. Sets the proportional weight when rolling which entry fills each slot.
Γ— (red) Remove this entry from the pool.

To get sparse fills, add a minecraft:air entry with a high weight β€” at roll time, slots that pick the air entry are left empty. Alternatively, lower the max slot cap.

Random durability and enchantment (per entry)

Since: v0.145.0 β€” PR #186

Each row can opt-in to spawn the rolled stack with random damage and/or a random vanilla enchantment. When at least one effect applies to the entry's item, a second sub-strip appears under the entry's main row with up to four extra cells:

[ Dur βœ“ ]  [ 100% ]  [ Ench βœ“ ]  [ 25% ]
Cell Action
Dur βœ“ / βœ— Master toggle. When βœ“, eligible damageable items roll a random damage value.
NN% (left) Click +5, shift-click -5. Range [0, 100] with wrap at the boundaries. Probability the durability effect applies on a given spawn. 0% never fires, 100% always fires.
Ench βœ“ / βœ— Master toggle. When βœ“, eligible enchantable items roll a vanilla-style random enchantment.
NN% (right) Same controls as the durability chance, but for the enchantment effect.

Defaults for new entries: Dur βœ“ 100%, Ench βœ“ 25%.

Per-item applicability β€” cells only render when they apply

The sub-strip only appears for items that support at least one of the two effects, and individual pairs only render when the item supports that specific effect:

Item class Sub-row shape
Neither damageable nor enchantable (cobblestone, bread, oak_log, …) No sub-row. Entry collapses back to a single-strip row.
Damageable but not enchantable (flint_and_steel, shears, …) Left two cells only: [Dur βœ“] [NN%]
Enchantable but not damageable (book, enchanted_book base, …) Right two cells only: [Ench βœ“] [NN%]
Both (swords, armor, tools, fishing_rod, …) All four cells

Click hit-tests honour the same gating β€” clicking the empty space where a hidden cell would be returns no hit. The raycast walks variable-height rows column-by-column so multi-column layouts handle mixed pools cleanly.

Roll details

When an entry is picked for a slot, the roller now:

  1. Materializes the ItemStack (was: hand-built Items NBT). Both the chest path and the decorated_pot path go through the shared rollItemStack helper.
  2. Per-effect chance roll: if randomDurability && stack.isDamageableItem(), the durability-chance salt mixer rolls a value in [0, 100) β€” if it falls below durabilityChance, the effect applies.
  3. Damage value: uniform [0, maxDamage - 1] (independent salt) β€” keeps the item usable, never spawns at "next hit and it breaks".
  4. Enchantment: vanilla EnchantmentHelper.enchantItem against EnchantmentTags.IN_ENCHANTING_TABLE (the non-treasure set β€” no Mending / Curses / Frost Walker). Level uniform [1, 30] from a seeded RandomSource.

All four new rolls (durability chance, durability value, enchantment chance, enchantment level seed) use distinct salts and feed the same splittable-mix that the K-roll and slot-pick rolls use, so the per-effect outcomes never correlate with the entry-pick or slot-pick rolls. Same world seed + same (carriageIndex, localPos, slot) β†’ identical loot, including the damage value and the rolled enchantments.

Items that fail the runtime gate (!isDamageableItem() or !isEnchantable()) simply skip the effect β€” the saved flag is harmless on the wrong item, which means future Minecraft versions that make new item classes damageable / enchantable would automatically pick up the flag's intent.

Potion entries β€” random placeholders, and authored potions spawn as placed

Since: v0.826.0

Potions used to lose their identity in a pool: the entry stored only the item id, so every potion came back as a bare bottle that the roller reinterpreted as "roll me a random effect". Randomisation is now explicit, and an authored potion spawns exactly as placed.

Three random placeholder items

Entry Pool
dungeontrain:random_potion Any vanilla potion, 6 escalating tiers of 4–5 effects
dungeontrain:random_good_potion Beneficial only, 4 tiers of 4 (Healing / Night Vision / Water Breathing / Leaping β†’ … β†’ Healing II / Swiftness II / Regeneration II / Strength II)
dungeontrain:random_bad_potion Harmful only, 4 tiers of 3 (Weakness / Slowness / Infested β†’ Poison / Weaving / Oozing β†’ Harming / Slowness II / Wind Charged β†’ Poison II / Harming II / Weaving)

All three live in the Dungeon Train β€” Narrative creative tab and are found by typing random in the Add search. At spawn time ContainerContentsRoller.bakeRandomPotion replaces the placeholder with a real drinkable / splash / lingering bottle. Every pick is deterministic per (seed, carriage, pos, slot) and each slot rolls independently, so one row landing several times in a chest usually gives different potions; the tier tables were widened for exactly that.

Per-entry sub-row: Scale and Form

A placeholder row shows a sub-row with two cells (potions are neither damageable nor enchantable, so the durability / enchantment cells never appear):

  • Scale βœ“ / βœ— β€” on (default), the power tier follows carriages travelled (50-carriage bands, same ramp as tipped arrows); off, the effect is drawn uniformly from every tier regardless of distance.
  • Form: Any / Potion / Splash / Lingering β€” click to step. Any (default) re-rolls the bottle per spawn; the other three pin it. The row icon switches to the pinned bottle so the row reads at a glance.

Per-form weighting works as it always did: one row per form, each with its own count and weight (the shipped brewing table is potion Γ—4 / splash Γ—3 / lingering Γ—1 at equal weight).

Authored potions spawn as authored

Adding a potion from your hand now records its vanilla potion on the entry (potionId), and the row label reads e.g. potion (healing) or potion (water). At spawn the stored potion is put back on the stack, so Water, Awkward, Mundane, Thick and every effect potion place exactly. A minecraft:potion added by id alone has no stored potion and spawns vanilla's Uncraftable Potion β€” no potion entry is ever randomised implicitly any more. Authors of older user-dir prefabs that still list a bare minecraft:potion should re-author them with a random entry.

Storage

Optional fields on the entry, omitted when at their default so old sidecars read unchanged: "potion": "minecraft:healing", "scale": false, "form": "splash" (pool sidecar + loot prefab JSON); p / s / f on the variant clipboard; one string, one boolean and one byte per entry on ContainerContentsSyncPacket. Edit ops TOGGLE_SCALE and CYCLE_POTION_FORM on ContainerContentsEditPacket. The shipped potionbarrel, brewing, end, lethershopchest prefabs and the craftshrine2 inline pool were migrated to random_potion with each original row's form pinned.

Roll algorithm at spawn time

Three independent deterministic rolls per container, all keyed on (worldSeed, carriageIndex, localPos, …):

  1. K (slot count) roll. K = uniform(min, effectiveMax), where effectiveMax = (fillMax == FILL_ALL ? containerSize : min(fillMax, containerSize)). Independent salt so K doesn't correlate with the slot-pick or count rolls.
  2. Slot subset roll. Seeded Fisher–Yates over [0, containerSize) produces a deterministic shuffled order; the first K indices become the filled slots.
  3. Per-slot entry pick + count roll. For each filled slot, weighted-random pick from the pool's entries (weight-proportional). If the picked entry is minecraft:air, leave the slot empty. Otherwise, roll the actual stack count uniformly in [1, entry.count] (independent salt).

Result: same world seed + same carriage index β†’ identical container contents. Different carriages at the same local pos β†’ different rolls. The same chest layout produces different inventories per playthrough at different seeds.

Container detection

Any block whose BlockEntity implements net.minecraft.world.Container qualifies. The menu controller probes the targeted block's BlockEntity at toggle time via EntityBlock.newBlockEntity(BlockPos.ZERO, state) and checks instanceof Container β€” so it works for vanilla Chest, Trapped Chest, Barrel, Hopper, Dispenser, Dropper, Shulker Box, Furnace family, and modded containers.

A non-container target produces an action-bar warning: Block has no inventory β€” look at a chest, barrel, dispenser, etc.

Cross-plot support

Like the Z menu, C resolves through BlockVariantPlot.resolveAt(player) so it works in all four editor plot types:

Plot Plot key (filename prefix) Resolver
Carriage variant carriage:<id> CarriageEditor.plotContaining
Carriage contents contents:<id> CarriageContentsEditor.plotContaining
Carriage part part:<kind>:<name> CarriagePartEditor.plotContaining
Track-side track:<kind>:<name> TrackPlotLocator.locate

Train Builder support

Since v0.799.0 (PR #1304) C also works in a Train Builder world, whose build resolves as a fifth plot:

Plot Plot key (filename prefix) Resolver
Train Builder build builder:carriage BuilderCarriagePlot.of (world data, not position)

That key is the same constant in every builder world, which is why the document is not stored in a file named after it: ContainerContentsStore takes a registered path override pointing at <world>/dungeontrain/builder/build.contents.json, inside the world save. Before this, every builder world read and wrote one shared builder_carriage.contents.json in the config dir. An overridden key also skips the dev-mode source write-through β€” an unnamed draft has no bundled resource to keep in lockstep with.

BuilderWorldSetup seeds the document from the template you open, and BuilderSave writes it onto the template you save as. Links to loot prefabs are carried as links, so a prefab edit still propagates.

Also since v0.799.0, the menu resolves from the targeted container (resolveAtPos) rather than the player's position, and once open follows the plot key the server recorded β€” which is stronger authorisation than the positional check it replaced. See the Z menu's note.

Placement pipeline

Container pools are wired into the carriage placement pipeline at three callsites: CarriageContentsTemplate.applyVariantBlocks, CarriageTemplate.applyVariantBlocks, CarriagePartTemplate.applyVariantBlocksForPlacement. Track and tunnel placements are not yet hooked β€” chest variants in those plots spawn empty pending a follow-up.

Sidecar storage

Per-plot JSON sidecar at config/dungeontrain/user/containers/<plotKey>.contents.json (the : in the plot key is replaced with _ for filename safety on Windows). Two-tier load:

  1. config/dungeontrain/user/containers/<plotKey>.contents.json (per-install authored)
  2. /data/dungeontrain/containers/<plotKey>.contents.json (bundled resource β€” ships authored pools with the mod)

Schema (v3 as of v0.145.0):

{
  "schemaVersion": 3,
  "pools": {
    "1,0,2": {
      "fillMin": 1,
      "fillMax": 5,
      "entries": [
        { "id": "minecraft:diamond_sword", "count": 1, "weight": 4,
          "randDur": true, "durChance": 100,
          "randEnch": true, "enchChance": 25 },
        { "id": "minecraft:cobblestone", "count": 10, "weight": 1,
          "randDur": true, "durChance": 100,
          "randEnch": true, "enchChance": 25 }
      ]
    },
    "0,1,0": {
      "entries": [
        { "id": "minecraft:diamond", "count": 3, "weight": 1,
          "randDur": true, "durChance": 100,
          "randEnch": true, "enchChance": 25 }
      ]
    }
  }
}

fillMin defaults to 0, fillMax defaults to -1 (sentinel for "all slots"). Both fields are omitted from disk when at default. A legacy fillCount: N field from an early dev iteration is read back as min == max == N.

The four random-effect fields default to (true, 100, true, 25). v2 files load with those defaults β€” no destructive migration. Per-cobblestone entries carry the flags too, but the runtime gate (isDamageableItem() / isEnchantable()) skips them at roll time so the data is harmless on items that don't apply.

Dev-mode bundled-resource path

When DevMode is enabled, saving the parent template (/dt save <id> for contents, parts) also writes the per-plot containers/<plotKey>.contents.json sidecar to src/main/resources/data/dungeontrain/containers/ β€” same write-through pattern as the block-variant sidecars. Empty stores are skipped so a save with no authored pools or links does not produce a sidecar file.

The promote path (/dt save default <id>) symmetrically copies the config-dir sidecar to source so existing config-only authoring can be shipped without re-saving. Sidecar promotion is best-effort: if the parent .nbt promote succeeds but the sidecar fails (missing, source tree unwritable), the parent promote is not rolled back β€” a warning is logged.

This means authored pools and links blocks committed to the repo ship with the next build's bundled resources and become every fresh install's default. The links block is the one most likely to silently disappear from the bundled tier if the sidecar is missing β€” chests authored with a link to a loot prefab appear correctly at runtime in the dev session but spawn empty (or with stale fallback data) for end users until the sidecar is shipped.

The barrel contents (the first authored pool, shipped in v0.74.0) was committed via this path. Container-link promotion across all contents/part plots was wired in PR #181 β€” prior to it, only barrel was correctly shipped because every other plot's sidecar was silently absent from the bundled tier.

Network

Protocol bumped 6 β†’ 9 in v0.74.0 β€” four new packets added in PR #103.

Packet Direction Payload
ContainerHotkeyPacket C2S held boolean
ContainerContentsMenuTogglePacket C2S open boolean
ContainerContentsSyncPacket S2C plotKey, localPos (or null = close), entries [itemId, count, weight, randDur, durChance, randEnch, enchChance], fillMin, fillMax, containerSize, anchor pos / right / up
ContainerContentsEditPacket C2S op (ADD / REMOVE / CLEAR / BUMP_WEIGHT / BUMP_COUNT / BUMP_FILL_MIN / BUMP_FILL_MAX / TOGGLE_RAND_DUR / TOGGLE_RAND_ENCH / BUMP_DUR_CHANCE / BUMP_ENCH_CHANCE / UNLINK), plotKey, localPos, entryIndex, itemId, delta

In v0.145.0 the sync packet's Entry record was extended with the four new fields and the edit packet's Op enum grew four new values (TOGGLE_RAND_DUR, TOGGLE_RAND_ENCH, BUMP_DUR_CHANCE, BUMP_ENCH_CHANCE). The packet TYPE id is unchanged β€” the layout grew, so old clients talking to a new server (or vice versa) would mis-decode. The editor is OP-only single-player by design.

The ADD op with empty itemId signals "use main-hand item" (server-side capture); a non-empty itemId is the explicit-id path used by the (currently unreachable) search screen.

Authoring flow

/dt editor contents enter barrel        # enter a contents plot
[walk to a chest in the plot]
C (tap)                                  # menu opens against the chest face
[hold a diamond sword in main hand]
Click "Add"                              # row added: diamond_sword | x1 | w1
Click on count cell                      # β†’ x2
Shift-click count                        # β†’ x1
Click "min" twice                        # β†’ 1, β†’ 2 (slots will fill 2..max)
Click "max"                              # all β†’ 0
Click "max" 5 more times                 # β†’ 5/27 (max 5 of the 27 slots)
[Spawn carriage in real run]             # the chest rolls 2..5 slots,
                                         # each with a weighted-random
                                         # entry, count 1..N per slot

Linking a container to a loot prefab

Since: v0.121.0 β€” PR #163

Containers in editor plots can be linked to a loot prefab. A linked container doesn't store its own pool data β€” it reads through to the prefab on every menu open, every spawn-time roll. Edits made via any linked container's menu propagate to every other linked container instantly.

How a link is created

Trigger Result
Click Save in the C-menu on an unlinked container, name it foo Container is now linked to foo. Action-bar: Saved loot 'foo' (linked).
Right-click a foo loot-prefab item from the creative tab inside an editor plot The placed container is linked to foo. Action-bar gains the (linked) suffix.
Right-click the same prefab item outside an editor plot No link β€” gameplay placement is unchanged. The chest still rolls items from the prefab once at placement, but template edits will not propagate to it.

Linked-state UI

When the targeted container is linked, the C-menu draws a sub-row beneath the header:

Linked: foo  X
  • The text is informational. Hovering highlights the row.
  • The right-side X cell unlinks. On unlink, the resolved pool is snapshotted to the local store first so the menu keeps showing the same entries.
  • The toolbar shifts down to make room.

Save and edit on a linked container

  • Save click: skips PrefabNameScreen. Action-bar: Linked to 'foo' β€” edits save automatically.
  • Add / Remove / Clear / count / weight / fill min / fill max: every edit is written straight to the LootPrefabStore JSON for the linked prefab, then propagated.

Propagation to other linked containers

Whenever a template is saved (via the C-menu Save, or via any edit on a linked container), ContainerContentsLinkPropagator scans every plot's link map and re-rolls the in-world BlockEntity at every linked position. A unique nanoTime salt is used for each propagation event so the deterministic roller produces visibly different items rather than re-deriving the placement-time roll.

Editor-only by construction: links only exist for containers placed/saved inside an editor plot, so the propagator never touches gameplay-placed containers.

Storage (schema v2)

Per position is AUTHORED XOR LINKED β€” the two states are mutually exclusive:

{
  "schemaVersion": 2,
  "pools": {
    "1,0,2": {
      "fillMin": 1,
      "fillMax": 5,
      "entries": [
        { "id": "minecraft:diamond", "count": 3, "weight": 1 }
      ]
    }
  },
  "links": {
    "0,1,0": "common_loot"
  }
}

pools and links never key the same position. Setting a link drops the local pool; unlinking snapshots the resolved pool back to local. The links block is omitted from disk when empty. v1 files (no links block) load with no links. Legacy v2 files written before this PR with both pools and links for the same position silently let the link win on read.

Networking (additive)

Packet Direction Change
ContainerContentsSyncPacket S2C Append optional linkedPrefabId (boolean + UTF when present).
ContainerContentsEditPacket C2S New Op.UNLINK op (no payload β€” just clears the link at localPos).

Vases (decorated_pot) β€” virtual single-slot container

Since: v0.78.1 β€” PR #113

minecraft:decorated_pot is also a valid C-menu target even though vanilla 1.20.1 doesn't give the pot a real Container BlockEntity. The roller treats it as a virtual single-slot container: the max ceiling reads /1 (vs /27 for chests), and the K-roll either picks 1 (an item rolls) or 0 (no item this carriage), giving the same min/max rarity dial used everywhere else.

Roll storage. Pots have no native item slot in 1.20.1, so the rolled ItemStack is written to a dt_pot_loot BE NBT field. A DecoratedPotBlockEntity mixin teaches the BE to round-trip the field across save/load β€” without it, vanilla's saveAdditional would strip the field on the next chunk save.

Delivery. When a vase is broken, a BlockEvent.BreakEvent handler reads dt_pot_loot and spills it via Containers.dropItemStack at the pot's world position β€” same call path as a broken chest. The vanilla sherd shards still drop alongside; the rolled item is additive. Trident-cracked pots and explosion breaks don't fire BlockEvent.BreakEvent and so don't drop the rolled item β€” out of scope at first ship.

Forward-compat. The native-Container check runs before the decorated_pot fallback. If the project is ever upgraded to a Minecraft version where DecoratedPotBlockEntity natively implements Container (1.20.5+ added ContainerSingleItem), the standard path picks it up and dt_pot_loot becomes dormant β€” no migration required.

Suspicious sand and gravel β€” authored dig loot

v0.1138.0 (PR #1803). The C menu also opens on suspicious sand and suspicious gravel, even though their BrushableBlockEntity isn't a Container (ContainerContentsRoller.isLootAuthorable; the menu reports 1 slot). An authored pool or a linked loot prefab rolls one item per carriage, using the same single-item roll as vases, so a min/max of 0–1 gives a chance of finding nothing. The item is written under the block entity's item key, and LootTable/LootTableSeed are removed, because vanilla prefers a stored table over item. Brushing the block then drops the authored item. A suspicious block with no pool keeps the vanilla archaeology table (desert_pyramid for sand, trail_ruins_common for gravel) via stampArchaeologyLoot. Vases' Luck-potion bonus doesn't apply to brushables.

Train interior preserved by the per-tick wipe

Since: v0.78.1 β€” PR #113

The train's per-tick entity wipe (TrainTickEvents) used to discard every non-player entity inside the train's current AABB, which silently nuked vase loot drops (and any other dropped item) on the same tick they spawned. The wipe now targets only the forward look-ahead slab β€” the runway in front of the train; carriage interiors are spared. The interior is still wiped exactly once at carriage-stamp time via CarriageContentsTemplate.discardEntitiesAt, so train contents stay stable after setup.

Implementation gotcha β€” TickWatcher name collision

The C-menu's hotkey class (ContainerHotkeyClient) originally followed the same pattern as VariantHotkeyClient (Z key), with a static inner class named TickWatcher. Forge's @Mod.EventBusSubscriber auto-registration silently dedupes inner static classes by simple name across a single mod β€” so two classes both named TickWatcher (in different outer classes) caused one of them to never receive events. Container's was processed first per the auto-subscribe log order, which silently broke the existing Z-tap menu.

Fix: rename Container's inner class to ContainerTickWatcher. The class is now documented with a docstring warning future copies of this pattern away from the same trap.

Files

Known limitations

  • Track and tunnel placements are not hooked. Chest variants placed in track:* or tunnel plots will spawn empty even with a pool authored. Follow-up issue tracking; the wiring is mechanically the same as the carriage callsites.
  • No item icons in the menu. Text-only labels (matches the existing block-variant menu's text-only style).
  • Item NBT is limited to random damage, random enchantments and a stored potion. Custom NBT (lore, attribute modifiers, hand-picked enchantments, custom names) is not yet authorable β€” the random effects fire from a fixed level range ([1, 30]) against the non-treasure enchantment tag. Fully fixed enchantments / damage / metadata is a follow-up.
  • No clipboard copy. Unlike the Z menu's Copy β†’ variant_clipboard flow, there's no clipboard item for pools yet. Pools are duplicated by hand-editing the sidecar JSON.
  • Per-entry count is a max, not a range. The lower bound is hardcoded at 1. To force exact counts, use count = N and accept the 1..N variance β€” full control of stack-size range is a follow-up.
  • Search screen is dormant. The ContainerContentsSearchScreen file ships but is unreachable from the toolbar (Add captures the held item directly, like the Z menu's Add). Files remain in case a search-by-typing path is added back.
⚠️ **GitHub.com Fallback** ⚠️