Capacity
This page covers EmakiStorage's capacity domain model: the capacity section of config.yml, the four capacity sources, the three-level stackLimit, and what happens to occupancy when capacity shrinks.
capacity
capacity:
base_slots: 45
max_slots: 1000
warn_entry_count: 5000
default_stack_limit: 100| Field | Type | Default | Description |
|---|---|---|---|
base_slots | int | 45 | Default open slot count (45 = 5 × 9, matching the gui.storage_rows default). |
max_slots | int | 1000 | Hard ceiling on total slots. 0 means unlimited. |
warn_entry_count | int | 5000 | Logs a console warning when the entry count exceeds this value. 0 disables the warning. |
default_stack_limit | long | 100 | Config-level per-slot ceiling. At this level 0 means unlimited. |
Four Capacity Sources
effectiveSlots is the sum of four independently persisted sources, then clamped:
effective = clamp(base_slots + permission tier + grantedSlots + purchasedSlots, 0, max_slots)The upper clamp is skipped when max_slots is 0. Because the four sources are stored separately, lowering base_slots never consumes slots a player was granted or purchased.
| Source | Comes from | Can be negative |
|---|---|---|
baseSlots | capacity.base_slots | No (negatives treated as 0) |
permissionSlots | Highest emakistorage.slots.<n> tier | No |
grantedSlots | /estorage slot grant, API grantSlotsAsync, action storage_grant_slot | Yes |
purchasedSlots | In-GUI paid expansion, action storage_unlock_slot | No |
Page count is derived from slot count and cannot be configured in reverse:
totalPages = max(1, ceil(effectiveSlots / slots per page))
reachablePages = max(1, ceil(usedSlots / slots per page))Page 1 is always reachable (an empty warehouse must still open and accept its first item); a page holding no entry at all cannot be paged into.
Confirm you have enough memory before setting max_slots to 0 (unlimited). The figure given in the config comment is roughly 100–200MB of heap for 100,000 entries on one player; that is an estimate and has not been measured.
Three-Level stackLimit
The per-slot ceiling resolves most-specific-first across three levels:
| Level | Stored in | Set through | Meaning of 0 |
|---|---|---|---|
| Entry | The matching entry in meta.yml | /estorage stacklimit slot, API setSlotStackLimitAsync, action storage_set_stacklimit with slot | Inherit the player level |
| Player | defaultStackLimit in meta.yml | /estorage stacklimit player, API setStackLimitAsync, action storage_set_stacklimit without slot | Inherit the config level |
| Config | capacity.default_stack_limit in config.yml | Config file | Unlimited (equivalent to Long.MAX_VALUE) |
The same 0 means different things per level: at entry and player level it means "inherit the next level", at config level it means "no limit". Resolution order is entry level > 0, then player level > 0, then the config level; a config level <= 0 yields Long.MAX_VALUE.
The emakistorage.stacklimit.<n> permission tier can also supply a player-level ceiling, again taking the highest value and not supporting wildcards.
One Item Across Several Slots
By default a full slot refuses the remainder. With behavior.multi_slot_stacking set to true, one item type may occupy further slots and keep storing.
behavior:
multi_slot_stacking: false # true = one entry may span several slotsWith a per-slot ceiling of 100 and 120 oak logs in hand:
| Setting | Result |
|---|---|
false (default) | 100 stored, the remaining 20 refused (slot_full) |
true | 120 stored, displayed as the two slots [100] [20] |
How the Occupied Span Is Derived
The occupied span is not persisted; it is derived from the amount on every read:
span = ceil(entry amount / per-slot ceiling)
usedSlots = sum of every entry's spanusedSlots has to count spans, otherwise a player could fill 90 slots with 45 spanning entries while only being charged 45, walking straight past max_slots.
When the per-slot ceiling is unlimited (default_stack_limit: 0 with no player- or entry-level override) span is always 1: a percentage of unlimited is meaningless, so there is no remainder to spill into a second slot.
Withdrawal and Merging
The slots of a spanning entry share one amount pool, so clicking any of them debits the entry total. Because span is derived, shrinking is automatic and there is no "merge" step:
120 units (ceiling 100) → [100] [20] 2 slots occupied
withdraw 20 from slot 1 → [100] 1 slot occupied, later entries roll forwardEntries themselves live in a gap-free compact list, so "later items roll forward" is the list's natural state and the window re-derives it on every repaint.
Display
Each slot shows its own amount rather than the entry total (slot 1 100/100, slot 2 20/100), matching how players read a vanilla container. A spanning entry appends one extra gui.entry.span_total line stating the entry total and how many slots it occupies.
Interaction With Other Mechanisms
| Surface | Behaviour |
|---|---|
| Total slot limit | Still enforced. Whatever does not fit is refused as before and never exceeds capacity.max_slots. |
%used_slots% (placeholder, /estorage info, the window's capacity line) | Follows the new semantics automatically: once enabled it reports occupied slots rather than entry count. |
/estorage stacklimit slot <n>, API setSlotStackLimitAsync, StorageEntrySnapshot.slotIndex | Still the entry index, not the displayed slot number. The ceiling belongs to the entry, and the entry index is the stable identity. With the setting off the two coincide. |
Search hit count %visible% | Still the number of matching entries, because players search for items rather than slots. |
| Overflow locking | Accumulates the boundary by span. An entry crossing the capacity boundary is locked as a whole (read-only, withdrawable), preserving zero loss. |
| Persistence | Format unchanged. Spanning only makes the entry amount larger, and the amount was already a variable-length integer. |
After Turning It Back Off
Going from true back to false loses no data: entries already above the per-slot ceiling still occupy one slot, display truncated at 100%, refuse new deposits and can be emptied normally.
Cost
With the setting off, usedSlots is O(1) (the entry count directly). Once on, every entry must be walked to accumulate spans, so the per-repaint cost rises for players with very many entries. Capacity is resolved once per repaint rather than once per slot, but the walk itself is unavoidable.
Shrinking and Overflow
When a lowered capacity leaves occupancy beyond the boundary, the outcome is decided by unlock.overflow_policy. The key itself is documented in the unlock section of Configuration.
The Four overflow_policy Values
| Value | Behaviour |
|---|---|
lock_readonly | Entries beyond the capacity boundary become read-only: withdrawable, not depositable, released as soon as they are emptied. Recommended. |
compact | Overflowing entries roll forward into free slots. Entries are already stored in a gap-free compact list, so "roll forward" is the list's natural state; whatever still does not fit degrades to lock_readonly. |
return_inventory | Tries to hand overflowing entries back to the player's inventory; whatever does not fit degrades to lock_readonly. Nothing is returned while the player is offline, entries are simply locked. |
reject_change | Refuses the shrink outright. |
All four policies are zero-loss. There is deliberately no drop or delete: irreversible data loss must never be triggered implicitly by adjusting a config value.
Overflow state is not persisted. It is derived from capacity versus occupancy and recomputed whenever capacity changes — login, reload, permission change, command grant — so a stored flag can never drift out of sync with the facts.
Related Pages
- Configuration: the remaining
config.ymlsections such asunlock,behaviorandpersistence. - GUI and Display: how
gui.storage_rowsdecides the slots per page. - Unlock Costs: pricing and the purchase flow behind
purchasedSlots. - Commands and Permissions:
/estorage slot,/estorage stacklimitand the permission tiers. - API and Integration: how
StorageCapacityfields map onto configuration keys.