Skip to content

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

yaml
capacity:
  base_slots: 45
  max_slots: 1000
  warn_entry_count: 5000
  default_stack_limit: 100
FieldTypeDefaultDescription
base_slotsint45Default open slot count (45 = 5 × 9, matching the gui.storage_rows default).
max_slotsint1000Hard ceiling on total slots. 0 means unlimited.
warn_entry_countint5000Logs a console warning when the entry count exceeds this value. 0 disables the warning.
default_stack_limitlong100Config-level per-slot ceiling. At this level 0 means unlimited.

Four Capacity Sources

effectiveSlots is the sum of four independently persisted sources, then clamped:

text
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.

SourceComes fromCan be negative
baseSlotscapacity.base_slotsNo (negatives treated as 0)
permissionSlotsHighest emakistorage.slots.<n> tierNo
grantedSlots/estorage slot grant, API grantSlotsAsync, action storage_grant_slotYes
purchasedSlotsIn-GUI paid expansion, action storage_unlock_slotNo

Page count is derived from slot count and cannot be configured in reverse:

text
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:

LevelStored inSet throughMeaning of 0
EntryThe matching entry in meta.yml/estorage stacklimit slot, API setSlotStackLimitAsync, action storage_set_stacklimit with slotInherit the player level
PlayerdefaultStackLimit in meta.yml/estorage stacklimit player, API setStackLimitAsync, action storage_set_stacklimit without slotInherit the config level
Configcapacity.default_stack_limit in config.ymlConfig fileUnlimited (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.

yaml
behavior:
  multi_slot_stacking: false # true = one entry may span several slots

With a per-slot ceiling of 100 and 120 oak logs in hand:

SettingResult
false (default)100 stored, the remaining 20 refused (slot_full)
true120 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:

text
span      = ceil(entry amount / per-slot ceiling)
usedSlots = sum of every entry's span

usedSlots 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:

text
120 units (ceiling 100)  →  [100] [20]      2 slots occupied
withdraw 20 from slot 1  →  [100]           1 slot occupied, later entries roll forward

Entries 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

SurfaceBehaviour
Total slot limitStill 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.slotIndexStill 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 lockingAccumulates the boundary by span. An entry crossing the capacity boundary is locked as a whole (read-only, withdrawable), preserving zero loss.
PersistenceFormat 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

ValueBehaviour
lock_readonlyEntries beyond the capacity boundary become read-only: withdrawable, not depositable, released as soon as they are emptied. Recommended.
compactOverflowing 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_inventoryTries 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_changeRefuses 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.